无法播放Android中MediaCodec编码的H.264 avc视频

Jam*_*hao 3 video android encoder h.264 mediacodec

背景:

我一直在实施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吨

我对H.264编码的机制了解不多。

码:

从此链接修改

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();
    }
}
}
Run Code Online (Sandbox Code Playgroud)

通过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();
    }
}
Run Code Online (Sandbox Code Playgroud)

释放相机时将其关闭:

private void releaseCamera() {
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
    if (mAvcEncoder != null) {
        mAvcEncoder.close();
    }
}
Run Code Online (Sandbox Code Playgroud)

fad*_*den 5

您正在保存原始的H.264流。您应该将其转换为.mp4格式。最简单的方法是使用MediaMuxer类(API 18+)。

你可以找到一个简单的例子bigflake的和更完整的例子Grafika

您将需要为每个帧提供演示时间戳。您可以根据所需的帧速率生成它们(例如bigf​​lake示例),也可以从源中获取它们(例如Grafika中的相机输入示例)。

编辑:对于API-18之前的设备(Android 4.1 / 4.2),MediaCodec更加难以使用。您不能使用Surface输入或MediaMuxer,并且缺少平台测试会导致一些不幸的不兼容性。 这个答案有一个概述。

在您的特定情况下,我会注意到您的示例代码正在尝试指定输入格式,但这没有效果-AVC编解码器定义了它接受的输入格式,并且您的应用必须查询它。您可能会发现编码视频中的颜色当前是错误的,因为Camera和MediaCodec没有任何共同的颜色格式(请参阅该答案以获取颜色交换代码)。