简体   繁体   English

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

[英]How to initialize MediaFormat to configure a MediaCodec to decode raw AAC data?

I have a strange issue with my StreamPlayer and I need any help I can get.我的 StreamPlayer 有一个奇怪的问题,我需要任何帮助。

The main goal I need to achieve is StreamPlayer which is able to play back MPEG-2 Transport Streams with smallest possible latency.我需要实现的主要目标是 StreamPlayer,它能够以尽可能小的延迟播放 MPEG-2 传输流。 For this I am following this approach:为此,我遵循这种方法:

The stream is parsed by a Java based TS Parser.该流由基于 Java 的 TS 解析器解析。 I have implemented a TSExtractor which is similar to the MediaExtractor and which works fine.我已经实现了一个类似于 MediaExtractor 并且工作正常的 TSExtractor。 I can receive all the media samples for a selected track the same way it is possible using the MediaExtractor with我可以像使用 MediaExtractor 一样接收所选曲目的所有媒体样本

extractor.readSampleData(...);
extractor.advance();

To decode the AAC data I want to create and configure an instance of MediaCodec.为了解码 AAC 数据,我想创建和配置 MediaCodec 的一个实例。 Using the MediaExtractor class this is usually done by使用 MediaExtractor 类通常由

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

As I have to initialize the MediaFormat in the TSExtractor.getTrackFormat(int track) method I use因为我必须在我使用的 TSExtractor.getTrackFormat(int track) 方法中初始化 MediaFormat

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

and because all my AAC samples include an ADTS I do并且因为我所有的 AAC 样本都包含一个 ADTS

mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1); 

After reading this post I finally add an ESDS frame using the "csd-0" key阅读完这篇文章后,我终于使用“csd-0”键添加了一个 ESDS 帧

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

where the values 0x11 and 0x90 are extracted from the ADTS.其中值 0x11 和 0x90 是从 ADTS 中提取的。

When I now want to decode the AAC samples the decoder posts当我现在想解码 AAC 样本时,解码器发布

AAC decoder returned error 4097, substituting silence

to the Log.到日志。

To verify that my TSExtractor extracts the samples correctly I recorded the same stream using VLC remuxing it to an mp4 file without transcoding so the raw stream is unchanged.为了验证我的 TSExtractor 是否正确提取了样本,我使用 VLC 将相同的流录制为 mp4 文件而不进行转码,因此原始流保持不变。 Now I can initialize the MediaExtractor with the recorded mp4 file and compare the samples created by my TSExtractor and the MediaExtractor.现在我可以用录制的 mp4 文件初始化 MediaExtractor 并比较我的 TSExtractor 和 MediaExtractor 创建的样本。 Using trail and error I found a very strange behavior:使用跟踪和错误,我发现了一个非常奇怪的行为:

When I configure the MediaCodec using the mediaFormat created by the MediaExtractor the MediaCodec decodes the AAC samples returned by my TSExtractor without any problems.当我使用 MediaExtractor 创建的 mediaFormat 配置 MediaCodec 时,MediaCodec 可以毫无问题地解码我的 TSExtractor 返回的 AAC 样本。 Comparing the MediaFormat, which basically wraps a HashMap, created by my TSExtractor and the one created by the MediaExtractor gives this differences:比较 MediaFormat,它基本上包装了一个 HashMap,由我的 TSExtractor 创建和由 MediaExtractor 创建的,给出了以下差异:

Created by MediaExtractor:由 MediaExtractor 创建:

mediaFormat: {max-input-size=1212, durationUs=77428875, is-adts=1, channel-count=2, mime=audio/mp4a-latm, csd-0=java.nio.ByteArrayBuffer[position=0,limit=2,capacity=2], sample-rate=48000} mediaFormat: {max-input-size=1212, durationUs=77428875, is-adts=1, channel-count=2, mime=audio/mp4a-latm, csd-0=java.nio.ByteArrayBuffer[position=0,limit =2,容量=2],采样率=48000}

Created by TSExtractor:由 TSExtractor 创建:

mediaFormat: {is-adts=1, channel-count=2, mime=audio/mp4a-latm, csd-0=java.nio.ByteArrayBuffer[position=2,limit=2,capacity=2], sample-rate=48000} mediaFormat: {is-adts=1, channel-count=2, mime=audio/mp4a-latm, csd-0=java.nio.ByteArrayBuffer[position=2,limit=2,capacity=2], sample-rate= 48000}

Even when I adopt the MediaFormat created by the TSExtractor to be similar to the one created by the MediaExtractor the decoder gives the same error using the self created and decodes without any problems using the other one.即使我采用 TSExtractor 创建的 MediaFormat 与 MediaExtractor 创建的类似,解码器也会使用自己创建的错误,并且使用另一个解码没有任何问题。

Any help would be really helpful.任何帮助都会非常有帮助。

I really don't know why, but it turns out that initializing the "csd-0" ByteBuffer this way我真的不知道为什么,但事实证明以这种方式初始化“csd-0”ByteBuffer

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

doesn't work, but initializing it this way不起作用,但以这种方式初始化

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

does.做。

BTW, comparing these two byteBuffers using顺便说一句,使用比较这两个 byteBuffers

bb1.equals(bb2);

returns true.返回真。

Very strange!很奇怪!

Values in csd-0 depends on ADTS header. csd-0 中的值取决于 ADTS 标头。

ADTS header length is up to 9 bytes. ADTS 标头长度最多为 9 个字节。 To generate csd-0 you need the second and the third byte of header.要生成 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 ) );

Now you got valid csd-0 for this aac audio stream.现在您获得了此 aac 音频流的有效 csd-0。

In the failing case u may need to call ByteBuffer's rewind method first.在失败的情况下,您可能需要先调用 ByteBuffer 的 rewind 方法。 If u look carefully u'll see the position is different between the MediaExtractor and the TSExtractor:如果您仔细观察,您会发现 MediaExtractor 和 TSExtractor 之间的位置不同:

csd-0=java.nio.ByteArrayBuffer[ position=0 ,limit=2,capacity=2] csd-0=java.nio.ByteArrayBuffer[位置=0 ,限制=2,容量=2]

vs对比

csd-0=java.nio.ByteArrayBuffer[ position=2 ,limit=2,capacity=2] csd-0=java.nio.ByteArrayBuffer[位置=2 ,限制=2,容量=2]

ByteBuffer's equals only compares the bytes after position until a mismatch; ByteBuffer 的 equals 只比较位置之后的字节,直到不匹配为止; in ur case one buffer is already positioned at the end hence no mismatch.在你的情况下,一个缓冲区已经位于末尾,因此没有不匹配。

Thanks for the above code for calculating CSD.感谢上面计算CSD的代码。 Unfortunately this was not working for me.不幸的是,这对我不起作用。 My decoder was failing with the above csd setting.我的解码器因上述 csd 设置而失败。 Finally I found the issue.最后我发现了这个问题。 According to the documentation first "5 bits " of CSD is the object type ( Profile).根据文档,CSD 的第一个“5 位”是对象类型(配置文件)。 Above code profile is added to only 4 bits.以上代码配置文件仅添加到 4 位。 So changing the code as below works fine for me所以改变下面的代码对我来说很好

  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)); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM