实时编码音频会导致声音跳过

Posted

技术标签:

【中文标题】实时编码音频会导致声音跳过【英文标题】:Encoding audio in realtime causes audible skipping 【发布时间】:2012-11-20 18:51:11 【问题描述】:

高级:

我以两种不同的方式使用相同的编码器。

方式#1:录制原始音频并保存到整个文件,然后在全部完成后压缩文件。 结果完美的音频

方式#2:记录原始音频,逐帧编码。 结果有声音跳过

为什么方法#2会导致跳过?

低级

方式#1的代码(写入FileOutputStream fos,并在所有写入完成后压缩)

public void writeSample(short[] buf) throws IOException 
            byte[] byteArray = Util.toByteArray(buf,false);
            bytesWritten += byteArray.length;
            fos.write(byteArray);
        

方式#2的代码

public void writeSample(short[] buf) throws IOException 
            byte[] byteArray = Util.toByteArray(buf,false);
            bytesWritten += byteArray.length;
            encoder.encode(byteArray);
        

几乎完美: 方法#2 中短 buf 的长度为 15360。由于这是一个奇数大小的数字,我采用了这种技术:但仍然有轻微的跳跃声:

      ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
      byte[] readme = new byte[4096];
      int count = bais.read(readme);
      while ( count != -1 ) 
        System.out.println("READING :"+count+ " bytes");
        if(bais.available() < 4096) 
          System.out.println ("LESS THAN 4096 available: "+bais.available());
          byte[] remain = new byte[bais.available()];
          bais.read(remain);
          aacEncoder.encode(remain);
          break;
        
        aacEncoder.encode(readme);
        count = bais.read(readme);
      

【问题讨论】:

为什么方法 #2 会导致跳过。逻辑上可能有什么不同? 【参考方案1】:

问题是这样的:编码器进行编码时,所花费的时间并不总是相同。有时它只是将一些数据藏起来以备后用,有时它实际上会进行大量的数字运算。它必须等待足够的音频来编码整个 MP3“帧”,否则它只会存储数据。

每次调用需要数字运算的新数据时,如果所需时间超过音频所代表的时间,则可能会发生丢失。

解决方案是让您的记录线程填充缓冲区并让第二个线程完成所有可能减慢速度或花费不可预测时间的工作。这包括编码和写入文件。

对于您的缓冲区,如果您的目标是 android 2.3 或更高版本,您可以使用管道I/O。从技术上讲,这不是环形缓冲区,因为它会阻塞,但根据我的经验,它运行得很好。 (这个api在android早期版本中可用,但是你不能设置缓冲区大小。Grrr!)

您可能会发现此链接有助于了解音频 IO 在概念上的实际工作原理:http://blog.bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html

【讨论】:

也许吧。然而,我挑战你的假设。当我在 AAC 中录制时,会发生跳过。但是,当我在 OGG 中录制时,不会发生跳过。 那是苹果和橘子:不同的编解码器、不同的算法、不同的 CPU 要求和块大小,所有这些都会导致不同的时序要求。最终,您不应该将任何具有未知时序要求的东西(包括文件写入、互斥锁和一堆其他东西)放在与播放 I/O 相同的线程中。如果它恰好起作用,那是因为你很幸运并且侥幸逃脱,而不是因为其他条件有问题。 我现在实现了一个生产/消费者线程模型,我仍然听到点击。所以我真的不认为这是一个线程问题。 任何代码示例的 IDK——我总是为工作/封闭源代码做这种事情。这是 PortAudio 邮件列表上的一个常见问题,因此您可以在那里搜索到一些东西(尽管那是 C)。 在这种情况下,生产者消费者比看起来更棘手。您必须确保您的生产者线程(记录器)永远不会等待消费者。还要确保缓冲区大小足够大。 IIRC,你需要一个巨大的 MP3 缓冲区大小,像 5 秒这样荒谬的东西才能真正可靠地播放/录制。您还可以增加 android AudioTrack 的延迟/缓冲区大小设置,这将有所帮助。

以上是关于实时编码音频会导致声音跳过的主要内容,如果未能解决你的问题,请参考以下文章

SceneKit - 我第一次播放声音时,音频会导致 cxa_throw 出现延迟

没有声音问题会杀死设备上所有应用程序的音频

资源被解释为文档,但在 Chrome 控制台中传输时出现 MIME 类型音频/mpeg 错误,导致无法播放声音

实时音频编解码之十一Opus编码

实时音频编解码之十 CELT编码器

如何在 Java 中播放 Opus 编码的音频?