如何在没有 MediaExtractor 的情况下为 H264 使用 MediaCodec
Posted
技术标签:
【中文标题】如何在没有 MediaExtractor 的情况下为 H264 使用 MediaCodec【英文标题】:How to use MediaCodec without MediaExtractor for H264 【发布时间】:2013-11-13 13:20:48 【问题描述】:我需要在没有 MediaExtractor 的情况下使用 MediaCodec,并且我正在使用 FileInputStream 读取文件。目前它不工作,它在屏幕上显示一个绿色的乱码。
这是完整的源代码:
FileInputStream in = new FileInputStream("/sdcard/sample.ts");
String mimeType = "video/avc";
MediaCodec decoder = MediaCodec.createDecoderByType(mimeType);
MediaFormat format = MediaFormat.createVideoFormat(mimeType, 1920, 1080);
byte[] header_sps = 0, 0, 0, 1, 103, 100, 0, 40, -84, 52, -59, 1, -32, 17, 31, 120, 11, 80, 16, 16, 31, 0, 0, 3, 3, -23, 0, 0, -22, 96, -108 ;
byte[] header_pps = 0, 0, 0, 1, 104, -18, 60, -128 ;
format.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
format.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080);
format.setInteger("durationUs", 63446722);
decoder.configure(format, surface, null, 0);
decoder.start();
ByteBuffer[] inputBuffers = decoder.getInputBuffers();
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
BufferInfo info = new BufferInfo();
boolean isEOS = false;
long startMs = System.currentTimeMillis();
while (!Thread.interrupted())
if (!isEOS)
int inIndex = decoder.dequeueInputBuffer(1000);
if (inIndex >= 0)
byte buffer2[] = new byte[18800 * 8 * 8 * 8];
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize;
sampleSize = in.read(buffer2, 0, 18800 * 4);
buffer.clear();
buffer.put(buffer2, 0, sampleSize);
buffer.clear();
if (sampleSize < 0)
decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isEOS = true;
else
decoder.queueInputBuffer(inIndex, 0, sampleSize, 0, 0);
int outIndex = decoder.dequeueOutputBuffer(info, 10000);
switch (outIndex)
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
outputBuffers = decoder.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.d("DecodeActivity", "dequeueOutputBuffer timed out! " + info);
break;
default:
ByteBuffer buffer = outputBuffers[outIndex];
Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);
while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs)
try
sleep(10);
catch (InterruptedException e)
e.printStackTrace();
break;
decoder.releaseOutputBuffer(outIndex, true);
break;
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
decoder.stop();
decoder.release();
如果我使用 MediaExtractor,一切正常。我在使用 MediaExtractor 时通过查看 MediaFormat 获得了 SPS/PPS 值。如果我删除下面的部分,屏幕上不会显示任何内容。
byte[] header_sps = 0, 0, 0, 1, 103, 100, 0, 40, -84, 52, -59, 1, -32, 17, 31, 120, 11, 80, 16, 16, 31, 0, 0, 3, 3, -23, 0, 0, -22, 96, -108 ;
byte[] header_pps = 0, 0, 0, 1, 104, -18, 60, -128 ;
format.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
format.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080);
format.setInteger("durationUs", 63446722);
我错过了什么?如何在没有 MediaExtractor 的情况下以编程方式获取 SPS/PPS 值?
【问题讨论】:
您能与我们分享更正后的代码吗?? SPS和PPS值你用的是标准值吗?就我而言,我以 NAL 单元的形式获取数据,因此我正在为 PPS 和 SPS 搜索 NAL 类型为 7 和 8 的帧。相反,我可以直接使用上述值吗?另外,我没有清楚地理解发布的答案。你能解释一下你的代码到底有什么问题吗?我将此代码用作我的方案的参考。 不,每个流都有自己的 SPS/PPS 值,您应该像上面提到的那样获取它们。问题是我正在向 MediaCodec (queueInputBuffer) 发送一个固定大小的缓冲区(大小为 18800 * 4),但您应该只发送完整的视频帧。现在清楚了吗? 有一种方法可以排除MediaExtractor
并将byte[]
帧(一个接一个)直接放入inputBuffer(ByteBuffer
)。这是你真正想要的吗?
【参考方案1】:
对于您的最后一个问题,即如何获得SPS
和PPS
值,您需要有一种机制从文件中读取相同的值。
如果您有一个elementary stream
文件,那么您需要解析该文件,识别NALU
标头并提取内容。
如果您有container
格式,则需要有一种机制来读取container
类型的file format
并提取信息。
如果您有streaming
输入,那么您可以从传入的SDP
信息中接收内容。
至于您当前的代码,我建议将SPS
和PPS
连接到一个缓冲区中,并将其提供给底层codec
,如下所示
byte[] csd_info = 0, 0, 0, 1, 103, 100, 0, 40, -84, 52, -59, 1, -32, 17, 31, 120, 11, 80, 16, 16, 31, 0, 0, 3, 3, -23, 0, 0, -22, 96, -108, 0, 0, 0, 1, 104, -18, 60, -128 ;
format.setByteBuffer("csd-0", ByteBuffer.wrap(csd_info));
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080);
format.setInteger("durationUs", 63446722);
【讨论】:
主要问题已通过其他(已接受)答案修复。但是感谢您回答第二个问题!为此 +1! MediaCodec 编解码器似乎在设备之间对 NAL 单元的构成有所不同。有些人希望在开头使用 0001 模式,有些人则不希望。其他人对您使用非 VCL 设备做什么很挑剔。 @PaulSteckler.. 解码器的实现正在发挥作用。一些编解码器坚持RTP
标准并且不处理起始码。这方面的一个示例是H.264
流式传输到Quicktime
,如果以RTP
标准规定的第一个字节为NALU
类型存在起始代码,则不显示视频。理想情况下,解码器应该能够搜索起始码并且足够健壮以处理变化。【参考方案2】:
我假设您正在读取原始 H.264 基本流而不是 MP4 文件。
看起来您正在向解码器提供固定大小的数据块。那是行不通的。您需要将单个访问单元放入每个缓冲区。
【讨论】:
这就是问题所在!现在我只为解码器提供视频数据包,它正在工作! @thiagolr 你能发布解决方案吗?你是如何解决这个问题的。我被困在decoder.dequeueOutputBuffer(info, 10000);
我总是得到-1 ...以上是关于如何在没有 MediaExtractor 的情况下为 H264 使用 MediaCodec的主要内容,如果未能解决你的问题,请参考以下文章
MediaExtractor、带有原始/资产文件的 MediaMetadataRetriever
音视频开发系列——全面了解Android MediaExtractor
[MediaExtractor与wav文件一起使用时抛出IllegalArgumentException
对于某些特定视频的音频,我使用 mediaExtractor 和 mediaFormat 获得一半的通道数和一半的采样率