录制视频时出现问题

时间:2015-11-12 10:36:03

标签: android opencv video ffmpeg javacv

我正在尝试使用javacv以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)的屏幕截图是

video resolution 480*480

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

639*480

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


    }

但使用此方法生成的视频仅包含绿色框架。

1 个答案:

答案 0 :(得分:1)

首先,它是预处理,而非后期处理视频。

我不知道为新版 javacv 调整解决方案需要进行哪些更改,我希望它们能够使库向后兼容。

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

enter image description here

这意味着您需要一个将每行复制到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);