【发布时间】:2015-08-21 18:16:25
【问题描述】:
背景:
两天来,我一直致力于实现类似 Vine 的录像机。首先,我尝试了 MediaRecorder。但是我需要的视频可能是由小视频片段组成的。此类不能用于录制短时视频剪辑。然后我找到了 MediaCodec、FFmpeg 和 JavaCV。 FFmpeg 和 JavaCV 可以解决这个问题。但我必须用许多库文件编译我的项目。它将生成一个非常大的 APK 文件。所以我更喜欢用MediaCodec来实现,虽然这个类只能在Android 4.1之后使用。 90% 的用户会感到满意。
结果:
我终于得到了编码文件,但无法播放。我通过FFprobe查看了信息,结果是这样的:
输入 #0,h264,来自“test.mp4”: 持续时间:不适用,比特率:不适用 流 #0:0:视频:h264(基线)、yuv420p、640x480、25 fps、25 tbr、1200k tbn、50 tbc
我对H.264编码的机制了解不多。
代码:
从此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();
}
}
}
通过相机的 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();
}
}
【问题讨论】:
标签: android video h.264 android-mediacodec encoder