【问题标题】:Issue in recording video录制视频的问题
【发布时间】:2016-02-13 15:50:39
【问题描述】:

我正在尝试使用 javacv 在 vi​​ne 中以 480*480 分辨率录制视频。作为起点,我使用了https://github.com/bytedeco/javacv/blob/master/samples/RecordActivity.java 中提供的示例视频正在被录制(但不是以所需的分辨率)并保存。

但问题是 android 原生不支持 480*480 分辨率。因此,需要进行一些预处理才能获得所需分辨率的视频。

因此,一旦我能够使用 javacv 提供的代码示例录制视频,接下来的挑战就是如何预处理视频。研究发现,当所需的最终图像宽度与记录的图像宽度相同时,可以进行有效裁剪。 SO问题Recording video on Android using JavaCV (Updated 2014 02 17)中提供了这样的解决方案。我按照该答案中的建议更改了 onPreviewFrame 方法。

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (audioRecord == null || audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            startTime = System.currentTimeMillis();
            return;
        }
        if (RECORD_LENGTH > 0) {
            int i = imagesIndex++ % images.length;
            yuvImage = images[i];
            timestamps[i] = 1000 * (System.currentTimeMillis() - startTime);
        }
        /* get video data */
        imageWidth = 640;
        imageHeight = 480    
        int finalImageHeight = 360;
        if (yuvImage != null && recording) {
            ByteBuffer bb = (ByteBuffer)yuvImage.image[0].position(0); // resets the buffer
            final int startY = imageWidth*(imageHeight-finalImageHeight)/2;
            final int lenY = imageWidth*finalImageHeight;
            bb.put(data, startY, lenY);
            final int startVU = imageWidth*imageHeight + imageWidth*(imageHeight-finalImageHeight)/4;
            final int lenVU = imageWidth* finalImageHeight/2;
            bb.put(data, startVU, lenVU);
            try {
                long t = 1000 * (System.currentTimeMillis() - startTime);
                if (t > recorder.getTimestamp()) {
                    recorder.setTimestamp(t);
                }
                recorder.record(yuvImage);
            } catch (FFmpegFrameRecorder.Exception e) {
                Log.e(LOG_TAG, "problem with recorder():", e);
            }
        }


    }
}

还请注意,此解决方案是为较旧版本的 javacv 提供的。生成的视频有覆盖 2/3 部分的黄色覆盖层。由于视频未正确裁剪,左侧也有空白部分。

所以我的问题是使用最新版本的 javacv 裁剪视频最合适的解决方案是什么?

按照 Alex Cohn 的建议进行更改后的代码

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (audioRecord == null || audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            startTime = System.currentTimeMillis();
            return;
        }
        if (RECORD_LENGTH > 0) {
            int i = imagesIndex++ % images.length;
            yuvImage = images[i];
            timestamps[i] = 1000 * (System.currentTimeMillis() - startTime);
        }
        /* get video data */
        imageWidth = 640;
        imageHeight = 480;       
        destWidth = 480;

        if (yuvImage != null && recording) {
            ByteBuffer bb = (ByteBuffer)yuvImage.image[0].position(0); // resets the buffer
            int start = 2*((imageWidth-destWidth)/4); // this must be even
            for (int row=0; row<imageHeight*3/2; row++) {
                bb.put(data, start, destWidth);
                start += imageWidth;
            }
            try {
                long t = 1000 * (System.currentTimeMillis() - startTime);
                if (t > recorder.getTimestamp()) {
                    recorder.setTimestamp(t);
                }
                recorder.record(yuvImage);
            } catch (FFmpegFrameRecorder.Exception e) {
                Log.e(LOG_TAG, "problem with recorder():", e);
            }
        }


    }

使用此代码(destWidth 480)生成的视频的屏幕截图是

接下来我尝试使用指定为 639 的 destWidth 捕获视频。结果是

当 destWidth 为 639 时,视频重复内容两次。 480时,内容重复5次,绿色叠加和失真更多。

此外,当 destWidth = imageWidth 时,可以正确捕获视频。即,对于 640*480,视频内容没有重复,也没有绿色覆盖。

将帧转换为 IplImage

当第一次问这个问题时,我没有提到 FFmpegFrameRecorder 中的记录方法现在接受 Frame 类型的对象,而之前它是 IplImage 对象。所以我尝试通过将 Frame 转换为 IplImage 来应用 Alex Cohn 的解决方案。

//---------------------------------------
// initialize ffmpeg_recorder
//---------------------------------------
private void initRecorder() {

    Log.w(LOG_TAG,"init recorder");

    imageWidth = 640;
    imageHeight = 480; 

    if (RECORD_LENGTH > 0) {
        imagesIndex = 0;
        images = new Frame[RECORD_LENGTH * frameRate];
        timestamps = new long[images.length];
        for (int i = 0; i < images.length; i++) {
            images[i] = new Frame(imageWidth, imageHeight, Frame.DEPTH_UBYTE, 2);
            timestamps[i] = -1;
        }
    } else if (yuvImage == null) {
        yuvImage = new Frame(imageWidth, imageHeight, Frame.DEPTH_UBYTE, 2);
        Log.i(LOG_TAG, "create yuvImage");
        OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
        yuvIplimage = converter.convert(yuvImage);

    }

    Log.i(LOG_TAG, "ffmpeg_url: " + ffmpeg_link);
    recorder = new FFmpegFrameRecorder(ffmpeg_link, imageWidth, imageHeight, 1);
    recorder.setFormat("flv");
    recorder.setSampleRate(sampleAudioRateInHz);
    // Set in the surface changed method
    recorder.setFrameRate(frameRate);

    Log.i(LOG_TAG, "recorder initialize success");

    audioRecordRunnable = new AudioRecordRunnable();
    audioThread = new Thread(audioRecordRunnable);
    runAudioThread = true;
}



@Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (audioRecord == null || audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            startTime = System.currentTimeMillis();
            return;
        }
        if (RECORD_LENGTH > 0) {
            int i = imagesIndex++ % images.length;
            yuvImage = images[i];
            timestamps[i] = 1000 * (System.currentTimeMillis() - startTime);
        }
        /* get video data */
        int destWidth = 640;

        if (yuvIplimage != null && recording) {
            ByteBuffer bb = yuvIplimage.getByteBuffer(); // resets the buffer
            int start = 2*((imageWidth-destWidth)/4); // this must be even
            for (int row=0; row<imageHeight*3/2; row++) {
                bb.put(data, start, destWidth);
                start += imageWidth;
            }
            try {
                long t = 1000 * (System.currentTimeMillis() - startTime);
                if (t > recorder.getTimestamp()) {
                    recorder.setTimestamp(t);
                }
                recorder.record(yuvImage);
            } catch (FFmpegFrameRecorder.Exception e) {
                Log.e(LOG_TAG, "problem with recorder():", e);
            }
        }


    }

但是用这种方法生成的视频只包含绿框。

【问题讨论】:

    标签: android opencv video ffmpeg javacv


    【解决方案1】:

    首先,它是对视频进行预处理,而不是后处理。

    我不知道您需要对 javacv 新版本的解决方案进行哪些更改,我希望他们保持库向后兼容。

    您的缓冲区宽 640 像素,高 480 像素。您想裁剪 480x480。

    这意味着您需要一个循环将每一行复制到 IplImage,如下所示:

    private int imageWidth = 640;
    private int imageHeight = 480;
    private int destWidth = 480;
    
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
    
    if (data.length != imageWidth*imageHeight) {
        Camera.Size sz = camera.getPreviewSize();
        imageWidth = sz.width;
        imageHeight = sz.height;
        destWidth = imageHeight;
    }
    
    ByteBuffer bb = (ByteBuffer)yuvImage.image[0].position(0); // resets the buffer
    int start = 2*((imageWidth-destWidth)/4); // this must be even
    for (int row=0; row<imageHeight*3/2; row++) {
        bb.put(data, start, destWidth);
        start += imageWidth;
    }
    recorder.record(yuvImage);
    

    【讨论】:

    • 感谢您的回复和更正(已编辑帖子以进行更改)我错过了提及现在 onPreviewFrame 方法正在使用 Frame 类型的对象而不是 IplImage。按照您的建议应用更改后,我已经用我的代码更新了问题。我在尝试一种方式时遇到绿框问题,在尝试另一种方式时重复出现失真的视频。请你看看,让我知道你的想法
    • 在你的情况下,destWidth 应该是 480。对不起,我没有在答案中指定这个。
    • 我尝试将 destWidth 设为 480(已编辑问题以包含此代码)结果视频的屏幕截图也包含在上述问题中。结果是内容重复了 5 次,并带有绿色覆盖。我还尝试了 destWidth 的其他值。仅当 destWidth == imageWidth 视频被正确捕获且内容不重复时。
    • 请确保相机实际配置为传送 640x480 的帧。这看起来不像。我添加了一段代码来正确处理。
    • 你是对的,这解释了你的扭曲。记录器帧应该在 initRecorder() 中使用destWidth 而不是imageWidth 进行初始化。但是camParams.setPreviewSize(imageWidth, imageHeight)不应该改。
    猜你喜欢
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-16
    相关资源
    最近更新 更多