7

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

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

流由基于 Java 的 TS Parser 解析。我已经实现了一个 TSExtractor,它与 MediaExtractor 类似并且工作正常。我可以像使用 MediaExtractor 一样接收所选曲目的所有媒体样本

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

要解码 AAC 数据,我想创建和配置 MediaCodec 的实例。使用 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); 

阅读这篇文章后,我终于使用“csd-0”键添加了一个 ESDS 框架

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

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

当我现在想解码 AAC 样本时,解码器帖子

AAC decoder returned error 4097, substituting silence

到日志。

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

当我使用 MediaExtractor 创建的 mediaFormat 配置 MediaCodec 时,MediaCodec 可以毫无问题地解码我的 TSExtractor 返回的 AAC 样本。比较由我的 TSExtractor 创建的基本上包含 HashMap 的 MediaFormat 和由 MediaExtractor 创建的 MediaFormat 给出了以下差异:

由 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,容量=2],采样率=48000}

由 TSExtractor 创建:

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

即使我采用 TSExtractor 创建的 MediaFormat 与 MediaExtractor 创建的 MediaFormat 相似,解码器使用自己创建的解码器也会出现相同的错误,而使用另一个解码器则没有任何问题。

任何帮助都会非常有帮助。

4

4 回答 4

6

我真的不知道为什么,但事实证明以这种方式初始化“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);

做。

顺便说一句,比较这两个 byteBuffers 使用

bb1.equals(bb2);

返回真。

很奇怪!

于 2013-09-16T14:24:18.113 回答
5

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。

于 2015-08-22T08:42:34.390 回答
3

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

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

对比

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

ByteBuffer 的 equals 只比较position之后的字节,直到不匹配;在您的情况下,一个缓冲区已经位于末尾,因此没有不匹配。

于 2013-11-24T00:54:02.650 回答
3

感谢上面计算 CSD 的代码。不幸的是,这对我不起作用。我的解码器因上述 csd 设置而失败。最后我发现了问题。根据文档,CSD 的第一个“5 位”是对象类型(配置文件)。以上代码配置文件仅添加到 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)); 
于 2016-03-29T08:02:09.950 回答