【问题标题】:The H.264 avc video encoded by MediaCodec in Android cannot be playedAndroid中MediaCodec编码的H.264 avc视频无法播放
【发布时间】: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


    【解决方案1】:

    您正在保存原始 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 没有任何共同的颜色格式(请参阅颜色交换代码的答案)。

    【讨论】:

    • MediaMuxer 只能在 Android 4.3 之后使用。这不是我的项目的选择。
    • 啊。我添加了一些关于 4.3 之前的设备的注释。
    • 感谢您告诉我潜在的风险。该代码只是一个演示。我会查询编解码器并尽快进行格式转换。
    • 非常感谢您的提示以及 grafika 的链接。 :)
    【解决方案2】:

    我相信您保存的数据是原始 h.264 数据。即使您使用 .mp4 扩展名命名文件,数据也不在视频容器中,例如 .mp4 文件的容器。这就是大多数媒体播放器无法播放该文件的原因。

    如果您为原始数据文件提供 .h264 或 .264 扩展名,您可能会在 vlc 方面取得一些成功。您应该尝试这样做来验证您获取的数据是原始 h.264 数据并且它是有效的。

    另外,阅读这个旧帖子可能会有所帮助:

    Decoding Raw H264 stream in android?

    在讨论中,它谈到了 SPS(序列参数集)。在处理来自摄像机的流式实时视频时,我以前必须在原始 h.264 数据的开头动态插入 SPS 以便对其进行处理。查看您保存的原始 h.264 二进制数据,看看它是否在开始时具有 SPS 记录。

    【讨论】:

    • 非常感谢。当我将此文件扩展名重命名为 .h264 时,VLC 可以播放它。我会尝试为原始数据制作一个容器。
    猜你喜欢
    • 2014-05-01
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 2015-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-19
    相关资源
    最近更新 更多