我正在尝试使用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)的屏幕截图是
接下来,我尝试捕获一个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);
}
}
}
但使用此方法生成的视频仅包含绿色框架。
答案 0 :(得分: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);