录制视频的问题

Issue in recording video

我正在尝试使用 javacv 像在 vine 中一样以 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)生成的视频的屏幕截图是

接下来我尝试捕捉一个 destWidth 指定为 639 的视频。结果是

当 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);
            }
        }


    }

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

首先,它是预处理,而不是post-处理视频。

我不知道你需要做哪些更改来调整新版本 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);