背景
我一直在努力实现像录像机一样的藤蔓两天。首先,我尝试了MediaRecorder。但我需要的视频可能是由小视频片段组成的。此类不能用于录制短时视频剪辑。然后我找到了MediaCodec,FFmpeg和JavaCV。 FFmpeg和JavaCV可以解决这个问题。但我必须使用许多库文件编译我的项目。它会生成一个非常大的APK文件。所以我更喜欢通过MediaCodec实现它,尽管这个类只能在Android 4.1之后使用。 90%的用户会满意。
结果:
我终于得到了编码文件,但无法播放。我通过FFprobe检查了信息,结果如下:
输入#0,h264,来自'test.mp4': 持续时间:N / A,比特率:N / A. 流#0:0:视频:h264(基线),yuv420p,640x480,25 fps,25 tbr,1200k tbn,50 tbc
我对H.264编码的机制知之甚少。
CODE:
从此link
修改public class AvcEncoder {
private static String TAG = AvcEncoder.class.getSimpleName();
private MediaCodec mediaCodec;
private BufferedOutputStream outputStream;
private int mWidth, mHeight;
private byte[] mDestData;
public AvcEncoder(int w, int h) {
mWidth = w;
mHeight = h;
Log.d(TAG, "Thread Id: " + Thread.currentThread().getId());
File f = new File("/sdcard/videos/test.mp4");
try {
outputStream = new BufferedOutputStream(new FileOutputStream(f));
Log.i("AvcEncoder", "outputStream initialized");
} catch (Exception e) {
e.printStackTrace();
}
try {
mediaCodec = MediaCodec.createEncoderByType("video/avc");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", w,
h);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 2000000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
// mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mDestData = new byte[w * h
* ImageFormat.getBitsPerPixel(ImageFormat.YV12) / 8];
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
}
public void close() {
try {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
// outputStream.flush();
outputStream.close();
} catch (IOException e) {
}
}
public void offerEncoder(byte[] input) {
try {
CameraUtils.transYV12toYUV420Planar(input, mDestData, mWidth,
mHeight);
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(mDestData);
mediaCodec.queueInputBuffer(inputBufferIndex, 0,
mDestData.length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,
0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
try {
outputStream.write(outData, 0, outData.length);
} catch (Exception e) {
Log.d("AvcEncoder", "Outputstream write failed");
e.printStackTrace();
}
// Log.i("AvcEncoder", outData.length + " bytes written");
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,
0);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
通过Camera的startPreview调用此类:
private void startPreview() {
if (mCamera == null) {
return;
}
try {
mCamera.setPreviewDisplay(mSurfaceView.getHolder());
Parameters p = mCamera.getParameters();
Size s = p.getPreviewSize();
int len = s.width * s.height
* ImageFormat.getBitsPerPixel(p.getPreviewFormat()) / 8;
mAvcEncoder = new AvcEncoder(s.width, s.height);
mCamera.addCallbackBuffer(new byte[len]);
mCamera.setPreviewCallbackWithBuffer(new PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
mAvcEncoder.offerEncoder(data);
mCamera.addCallbackBuffer(data);
}
});
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
释放相机时关闭它:
private void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if (mAvcEncoder != null) {
mAvcEncoder.close();
}
}
答案 0 :(得分:5)
您正在保存原始H.264流。您应该将其转换为.mp4格式。最简单的方法是使用MediaMuxer类(API 18 +)。
您可以在bigflake上找到一个简单的示例,在Grafika中找到更完整的示例。
您需要为每个帧提供演示时间戳。您可以根据所需的帧速率生成它们(如bigflake示例)或从源获取它们(如Grafika中的相机输入示例)。
编辑:对于API-18之前的设备(Android 4.1 / 4.2),MediaCodec使用起来要困难得多。您不能使用Surface输入或MediaMuxer,缺乏平台测试会导致一些不幸的不兼容性。 This answer有一个概述。
在您的具体情况下,我会注意到您的示例代码正在尝试指定输入格式,但这没有任何效果 - AVC编解码器定义了它接受的输入格式,并且您的应用必须查询它。您可能会发现编码视频中的颜色为currently wrong,因为Camera和MediaCodec没有任何共同的颜色格式(请参阅颜色交换代码的答案)。
答案 1 :(得分:3)
我相信您保存的数据是原始h.264数据。即使您使用.mp4扩展名命名文件,数据也不在视频容器中,例如.mp4文件的容器。这就是大多数媒体播放器无法播放文件的原因。
如果您为原始数据文件提供.h264或.264扩展名,则vlc可能会取得一些成功。您应该尝试这样做以验证您获得的数据是原始h.264数据并且它是有效的。
此外,它可能有助于阅读这个旧线程:
Decoding Raw H264 stream in android?
在讨论中,它讨论了SPS(序列参数集)。当处理来自摄像机的流式直播视频时,我之前必须在原始h.264数据的开始处动态插入SPS以便处理它。查看您要保存的原始h.264二进制数据,以查看它是否在开始时具有SPS记录。