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

时间:2015-06-07 02:34:29

标签: android video h.264 mediacodec encoder

背景

我一直在努力实现像录像机一样的藤蔓两天。首先,我尝试了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();
    }
}

2 个答案:

答案 0 :(得分:5)

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

您可以在bigflake上找到一个简单的示例,在Grafika中找到更完整的示例。

您需要为每个帧提供演示时间戳。您可以根据所需的帧速率生成它们(如bigf​​lake示例)或从源获取它们(如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记录。