如何初始化MediaFormat以配置MediaCodec以解码原始AAC数据?

时间:2013-09-13 11:04:11

标签: android mediacodec

我的StreamPlayer有一个奇怪的问题,我需要任何帮助。

我需要实现的主要目标是StreamPlayer,它能够以尽可能小的延迟播放MPEG-2传输流。为此,我遵循这种方法:

流由基于Java的TS Parser解析。我已经实现了一个类似于MediaExtractor的TSExtractor,它工作正常。我可以像使用带

的MediaExtractor一样接收选定曲目的所有媒体样本
extractor.readSampleData(...);
extractor.advance();

要解码我想要创建和配置MediaCodec实例的AAC数据。使用MediaExtractor类通常由

完成
MediaFormat mediaFormat = extractor.getTrackFormat(i);
decoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
decoder.configure(mediaFormat, null, null, 0);

由于我必须在TSExtractor.getTrackFormat(int track)方法中初始化MediaFormat,我使用

MediaFormat mf = MediaFormat.createAudioFormat ("audio/mp4a-latm", getSampleRate(), getChannelCount());

并且因为我的所有AAC样本都包括我做的ADTS

mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1); 

阅读this帖后,我最后使用“csd-0”键添加ESDS帧

mediaFormat.setByteBuffer("csd-0", ByteBuffer.allocate(2).put(new byte[]{(byte) 0x11, (byte)0x90}));

其中从ADTS中提取值0x11和0x90。

当我现在要解码AAC样本时,解码器发布

AAC decoder returned error 4097, substituting silence

到日志。

为了验证我的TSExtractor正确提取样本我使用VLC将相同的流重新转换为mp4文件而不进行转码,因此原始流不变。现在我可以用记录的mp4文件初始化MediaExtractor,并比较我的TSExtractor和MediaExtractor创建的样本。使用跟踪和错误我发现了一个非常奇怪的行为:

当我使用MediaExtractor创建的mediaFormat配置MediaCodec时,MediaCodec会对我的TSExtractor返回的AAC样本进行解码而不会出现任何问题。比较MediaFormat,它基本上包装了由我的TSExtractor创建的HashMap和由MediaExtractor创建的那个,给出了这些差异:

由MediaExtractor创建:

  

mediaFormat:{max-input-size = 1212,durationUs = 77428875,is-adts = 1,   channel-count = 2,mime = audio / mp4a-latm,   CSD-0 = java.nio.ByteArrayBuffer [位置= 0,极限= 2,容量= 2],   采样率= 48000}

由TSExtractor创建:

  

mediaFormat:{is-adts = 1,channel-count = 2,mime = audio / mp4a-latm,   CSD-0 = java.nio.ByteArrayBuffer [位置= 2,极限= 2,容量= 2],   采样率= 48000}

即使我采用TSExtractor创建的MediaFormat与MediaExtractor创建的MediaFormat类似,解码器也会使用self创建并使用另一个解码而没有任何问题进行解码。

任何帮助都会非常有用。

4 个答案:

答案 0 :(得分:5)

我真的不知道为什么,但事实证明以这种方式初始化“csd-0”ByteBuffer

mediaFormat.setByteBuffer("csd-0", ByteBuffer.allocate(2).put(new byte[]{(byte) 0x11, (byte)0x90}));

不起作用,但是以这种方式初始化

byte[] bytes = new byte[]{(byte) 0x11, (byte)0x90};
ByteBuffer bb = ByteBuffer.wrap(bytes);
mediaFormat.setByteBuffer("csd-0", bb);

确实

BTW,使用

比较这两个byteBuffers
bb1.equals(bb2);

返回true。

很奇怪!

答案 1 :(得分:3)

感谢上面的计算CSD的代码。不幸的是,这对我不起作用。我的解码器失败了上面的csd设置。最后我发现了这个问题。根据文档第一" 5位" CSD是对象类型(Profile)。以上代码配置文件仅添加到4位。所以更改下面的代码对我来说很好

  int profile = (header[2] & 0xC0) >> 6;
  int srate = (header[2] & 0x3C) >> 2;
  int channel = ((header[2] & 0x01) << 2) | ((header.[3] & 0xC0) >> 6)

  ByteBuffer csd = ByteBuffer.allocate(2);
  csd.put(0, (byte)(profile << 3 | srate >> 1));
  csd.put(1, (byte)((srate & 0x01) << 7 | channel << 3)); 

答案 2 :(得分:2)

在失败的情况下,你可能需要先调用ByteBuffer的倒带方法。如果你仔细看,你会发现MediaExtractor和TSExtractor之间的位置不同:

CSD-0 = java.nio.ByteArrayBuffer [<强>位置= 0 下,极限= 2,容量= 2]

VS

CSD-0 = java.nio.ByteArrayBuffer [<强>位置= 2 下,极限= 2,容量= 2]

ByteBuffer的equals仅比较位置之后的字节,直到不匹配为止;在你的情况下,一个缓冲区已经定位在最后,因此没有不匹配。

答案 3 :(得分:2)

csd-0中的值取决于ADTS标头。

ADTS标头长度最多为9个字节。要生成csd-0,您需要标题的第二个和第三个字节。

int profile = (header[2] & 0xC0) >> 6;
int srate = (header[2] & 0x3C) >> 2;
int channel = ((header[2] & 0x01) << 2) | ((header.[3] & 0xC0) >> 6)

ByteBuffer csd = ByteBuffer.allocate(2);
csd.put(0, (byte)( ((profile + 1) << 3) | srate >> 1 ) );
csd.put(1, (byte)( ((srate << 7) & 0x80) | channel << 3 ) );

现在你为这个aac音频流获得了有效的csd-0。