如何修复使用 MediaMuxer 和 MediaCodec 混合后损坏的视频(绿色帧)

问题描述 投票:0回答:2

我正在尝试从 NalUnits 数组中编码 mp4 视频,其中每个单元都是由 byte[] 表示的帧,该帧是从 rtsp 流的 rtp 数据包中保存的。我以 30fps 编码 451 帧,其中第一帧数据是 spp 和 pps 帧的组合。这是我当前的配置:

Width: 720
Height: 480
Bitrate: 10_000_000
Fps: 30
Colorformat: COLOR_FormatYUV420Flexible
Key I Frame Interval: 1

这是我对配置编码器进行编码的方法:

public boolean createMp4Video(FrameQueue.Frame[] frames, File dir) {
    try {
        // Set up MediaMuxer
        Date date = new Date();
        File file = new File(dir, date + "-recording.mp4");
        this.muxer = new MediaMuxer(file.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

        // Set up MediaCodec
        mMediaFormat = MediaFormat.createVideoFormat(codecName, width, height);
        mMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
        mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
        mMediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
        mMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

        codec = MediaCodec.createEncoderByType(codecName);
        codec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

        codec.start();

        MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();

        // Process frames
        for (int i = 0; i < frames.length; i++) {
            if (i == frames.length - 1) { // if last frame, add end of stream flag
                muxFrame(frames[i].getData(), i, true);
            } else {
                muxFrame(frames[i].getData()), i, false);                     
        }

            // Stop and release resources
            muxer.stop();
            muxer.release();
            codec.stop();
            codec.release();
            listener.onMp4VideoCreated();
            return true;
        } catch (IOException e) {
            return false;
        }
    }

我使用简单的 for 循环遍历所有帧,并调用以下函数来方法:

private void muxFrame(byte[] frameData, int encodeIndex, boolean isVideoEOS) {
    long presentationTimeUs = 1000000 / framerate * encodeIndex

    int inputBufferIndex = codec.dequeueInputBuffer(-1);
    if (inputBufferIndex >= 0) {
        ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
        inputBuffer.put(frameData);
        codec.queueInputBuffer(inputBufferIndex, 0, frameData.length, presentationTimeUs, 0);
    }

    // Create a MediaCodec.BufferInfo to hold the frame information
    MediaCodec.BufferInfo muxerOutputBufferInfo = new MediaCodec.BufferInfo();

    int outputBufferIndex = codec.dequeueOutputBuffer(muxerOutputBufferInfo, 1000);

    switch (outputBufferIndex) {
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            MediaFormat newFormat = codec.getOutputFormat();
            mVideoTrack = muxer.addTrack(newFormat);
            muxer.start();
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            break;
        default:
            // Set the size, presentation time, and flags of the muxerOutputBufferInfo
            muxerOutputBufferInfo.size = frameData.length;
            muxerOutputBufferInfo.offset = 0;
            muxerOutputBufferInfo.flags = isVideoEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : MediaCodec.BUFFER_FLAG_KEY_FRAME;
            muxerOutputBufferInfo.presentationTimeUs = presentationTimeUs;
            // Write the frame data to the muxer
            muxer.writeSampleData(mVideoTrack, ByteBuffer.wrap(frameData), muxerOutputBufferInfo);
            codec.releaseOutputBuffer(outputBufferIndex, false);
            break;
    }


}

我可以保存视频并播放它,但所有帧都已损坏并且呈绿色https://dropmefiles.com/rn6I0

这是我编码时的日志:

I/OMXClient: IOmx service obtained
W/OMXUtils: do not know color format 0x7f000200 = 2130706944
W/OMXUtils: do not know color format 0x7f000789 = 2130708361
I/ACodec: [OMX.MTK.VIDEO.ENCODER.AVC] using color format 0x7f420888 in place of 0x7f420888
I/ACodec_vilte: set I frame rate
I/ACodec_vilte: set framerate
I/ACodec_vilte: setMTKParameters, width: 720
I/ACodec_vilte: setMTKParameters, height: 480
I/ACodec: setupAVCEncoderParameters with [profile: Baseline] [level: Level41]
I/ACodec: [OMX.MTK.VIDEO.ENCODER.AVC] cannot encode color aspects. Ignoring.
I/ACodec: [OMX.MTK.VIDEO.ENCODER.AVC] cannot encode HDR static metadata. Ignoring.
I/ACodec: setupVideoEncoder succeeded
I/ACodec_vilte: set I frame rate
I/ACodec_vilte: set framerate
I/ACodec_vilte: setMTKParameters, width: 720
I/ACodec_vilte: setMTKParameters, height: 480
I/general: [33] Configuring video encoder - 450 frames
I/general: [33] Video encoding in progress
D/MPEG4Writer: start+ 797
I/MPEG4Writer: limits: 4294967295/0 bytes/us, bit rate: -1 bps and the estimated moov size 3191 bytes
D/MPEG4Writer: start+ 2403
D/MPEG4Writer: start- 2481
D/MPEG4Writer: start- 964
I/MPEG4Writer: setStartTimestampUs: 166665
I/MPEG4Writer: Earliest track starting time: 166665
D/MPEG4Writer: Video mStartTimestampUs=166665us
D/MPEG4Writer: reset+ 1081
D/MPEG4Writer: Video track stopping. Stop source
D/MPEG4Writer: Video track source stopping
D/MPEG4Writer: Video track source stopped
I/MPEG4Writer: Received total/0-length (439/0) buffers and encoded 439 frames. - Video
D/MPEG4Writer: Video track stopped. Stop source
D/MPEG4Writer: Stopping writer thread
D/MPEG4Writer: 0 chunks are written in the last batch
D/MPEG4Writer: Writer thread stopped
I/MPEG4Writer: Ajust the moov start time from 166665 us -> 166665 us
I/MPEG4Writer: The mp4 file will not be streamable.
D/MPEG4Writer: reset- 1187
D/MPEG4Writer: reset+ 1081
D/MPEG4Writer: Video track stopping. Stop source
I/general: [33] MP4 video created successfully
java android video-processing mediamuxer
2个回答
1
投票

为什么视频有损坏的帧?共享字节来看

1)每一帧都是关键帧
我想知道为什么你的 MP4 列出关键帧的部分,

stss
原子,将每个单帧列为“关键帧”类型(为什么没有 P 帧和 B 帧?)但后来我看到你的代码有这个命令:

mMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

由于您是mushing(复制/粘贴现有媒体字节),因此media有自己的间隔,应将其写入输出MP4中... Muxing过程不需要给予新的关键帧间隔。

解决方案:
测试禁用(或评论

//
)该行的可能修复,
同样
//
禁用
bitrate
设置。

2)您的第一帧不是关键帧
您发送到复用器的第一个视频帧不是关键帧。
它在 MP4 元数据中被标记为关键帧(在 stss 原子处),但它的实际字节是 P 帧类型。这意味着:在 start-code

0, 0, 0, 1
之后,您的 NAL 标头被发现为十六进制:
42
,或十进制:
65
)。请注意,因为十进制
65
与十六进制
65
不同,因此如果您忘记转换,并认为十进制
65
意味着此 NALU 是关键帧,可能会造成混乱。

解决方案:
确保发送带有小数的 NAL 单位:

0, 0, 0, 1, 101
(或十六进制:
00, 00, 00, 01, 65
)。

101
将其标记为关键帧NALU(标头字节值为:
0x65
或十进制值:
101
)。

3)你的SPS和PPS有误
问题又是 SPS 和 PPS 错误......
在 MP4 内部,SPS/PPS 数据进入 avcC 部分(其中保存“AVC 配置”,AVC 只是 H264 的替代名称)。

如果我通过复制/粘贴原始 NALU 中的字节来手动覆盖

avcC
部分的一部分,那么损坏的 MP4 将显示完美的图片(而不是混乱的颜色)。

解决方案:

选项 A: 尝试设置包含 SPS 和 PPS 的

MediaFormat

这意味着根据这些放置编解码器特定数据的条目:

Format      CSD buffer #0                       CSD buffer #1                       CSD buffer #2
H.264 AVC   SPS (Sequence Parameter Sets)       PPS (Picture Parameter Sets)        Not Used

选项 B:在最坏的情况下,您可能必须在 Android 代码创建 MP4 文件后手动修复字节(例如:文件创建代码完成后,运行另一个函数来查找并覆盖一些大约新创建的 MP4 内的现有 SPS 和 PPS 的 25 字节)。希望不需要作为解决方案。

我无法测试 Android 代码,请检查此示例代码是否创建了可用的 MP4:

(1) 在十进制之间转换为十六进制(因为字节被写为十六进制)

//# check Byte as Decimal
int temp_int = (NALU_SPS[4] & 0xFF); //where NALU_SPS is an Array.
System.out.println("NALU Decimal: " + temp_int );

//# check Byte as Hex
String temp_str = Integer.toString( temp_int, 16 );
temp_str = (temp_str.length() < 2) ? ("0"+temp_str) : temp_str;
System.out.println("NALU Byte: 0x" + temp_str );

在仔细检查数组值的字节值时,请使用上面的内容来转换数组值,因为教程和 H264 规范将提到十六进制格式的字节值。

0xFF
这样的字节示例是十六进制
FF
并且是十进制
255

(2) 试试这个

MediaFormat
设置。

//# Set up MediaCodec
mMediaFormat = MediaFormat.createVideoFormat(codecName, width, height);
mMediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);

//# Your SPS/PPS in an Array. Can be: byte[] ...or Else: int[]
byte[] NALU_SPS = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x29, 0xE2, 0x90, 0x16, 0x87, 0xB6, 0x06, 0xAC, 0x18, 0x04, 0x1B, 0x87, 0x89, 0x11, 0x50 };
byte[] NALU_PPS = { 0x00, 0x00, 0x00, 0x01, 0x68, 0xCE, 0x3C, 0x80 };

//# send SPS/PPS to the Muxer
mMediaFormat.setByteBuffer( "csd-0", ByteBuffer.wrap( NALU_SPS ) );
mMediaFormat.setByteBuffer( "csd-1", ByteBuffer.wrap( NALU_PPS ) );

//# This might not be needed (since Muxer could get from SPS/PPS)
mMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);

//# test with these disabled
//mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
//mMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

0
投票

我的建议是不要使用 MediaMuxer,因为它似乎会产生错误的输出。使用 mp4v2 库,它将产生正确的输出并在所有设备上一致地工作。

© www.soinside.com 2019 - 2024. All rights reserved.