我正在尝试从 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
为什么视频有损坏的帧?从共享字节来看:
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);
我的建议是不要使用 MediaMuxer,因为它似乎会产生错误的输出。使用 mp4v2 库,它将产生正确的输出并在所有设备上一致地工作。