7

我知道网上有很多资源解释如何解交织 PCM 数据。在我目前的项目过程中,我看过其中的大部分……但我没有音频处理方面的背景,而且我很难找到关于这种常见音频形式的存储方式的详细说明。

我确实知道我的音频将有两个通道,因此样本将以 [left][right][left][right] 的格式存储......我不明白这到底是什么意思。我还读到每个样本都以 [left MSB][left LSB][right MSB][right LSB] 格式存储。这是否意味着每个 16 位整数实际上编码了两个 8 位帧,或者每个 16 位整数都有其自己的帧用于左通道或右通道?

谢谢大家。任何帮助表示赞赏。

编辑:如果您选择给出示例,请参阅以下内容。

方法上下文

具体来说,我要做的是将交错的 short[] 转换为两个 float[],每个代表左声道或右声道。我将在 Java 中实现它。

public static float[][] deinterleaveAudioData(short[] interleavedData) {
    //initialize the channel arrays
    float[] left = new float[interleavedData.length / 2];
    float[] right = new float[interleavedData.length / 2];
    //iterate through the buffer
    for (int i = 0; i < interleavedData.length; i++) {
        //THIS IS WHERE I DON'T KNOW WHAT TO DO
    }
    //return the separated left and right channels
    return new float[][]{left, right};
}

我目前的实施

我试过播放由此产生的音频。它非常接近,足够接近,你可以理解一首歌的歌词,但显然仍然不是正确的方法。

public static float[][] deinterleaveAudioData(short[] interleavedData) {
    //initialize the channel arrays
    float[] left = new float[interleavedData.length / 2];
    float[] right = new float[interleavedData.length / 2];
    //iterate through the buffer
    for (int i = 0; i < left.length; i++) {
        left[i] = (float) interleavedData[2 * i];
        right[i] = (float) interleavedData[2 * i + 1];
    }
    //return the separated left and right channels
    return new float[][]{left, right};
}

格式

如果有人想了解有关音频格式的更多信息,以下就是我所拥有的一切。

  • 格式为 PCM 2 通道交错式大端线性 int16
  • 采样率为 44100
  • 每个 short[] 缓冲区的短裤数为 2048
  • 每个 short[] 缓冲区的帧数为 1024
  • 每个数据包的帧数为 1
4

4 回答 4

14

我确实知道我的音频将有两个通道,因此样本将以 [left][right][left][right] 的格式存储......我不明白这到底是什么意思。

交错的 PCM 数据按通道顺序存储每个通道一个样本,然后再继续下一个样本。PCM由每个通道的一组样本组成。如果您有左右声道的立体声音频,则每个样本中的一个样本一起构成一帧。

  • 第 0 帧:[左样本][右样本]
  • 第一帧:[左样本][右样本]
  • 第 2 帧:[左样本][右样本]
  • 第 3 帧:[左样本][右样本]
  • ETC...

每个样本都是瞬时压力的测量和数字量化。也就是说,如果每个样本有 8 位,则可以对压力进行 256 种可能的精度级别的采样。知道声波是……波……有波峰和波谷,我们将希望能够测量到中心的距离。因此,我们可以将中心定义在 127 左右,然后从那里减去和加法(0 到 255,无符号),或者我们可以将这 8 位视为有符号(相同的值,只是对它们的不同解释)并从 -128 到 127。

对于单通道(单声道)音频,每个样本使用 8 位,我们每个样本使用一个字节,这意味着以 44.1kHz 采样的一秒音频恰好使用 44,100 字节的存储空间。

现在,让我们假设每个样本有 8 位,但在 44.1.kHz 的立体声中。每个其他字节都将用于左侧,每个其他字节都将用于 R。

LRLRLRLRLRLRLRLRLRLRLR...

将其缩放到 16 位,每个样本有两个字节(样本设置有括号[],空格表示帧边界)

[LL][RR] [LL][RR] [LL][RR] [LL][RR] [LL][RR] [LL][RR]...

我还读到每个样本都以 [left MSB][left LSB][right MSB][right LSB] 格式存储。

不必要。音频可以以任何字节顺序存储。小端是最常见的,但这不是一个神奇的规则。我确实认为虽然所有频道总是按顺序排列,但在大多数情况下,左前方将是频道 0。

这是否意味着每个 16 位整数实际上编码了两个 8 位帧,或者每个 16 位整数是其自己的帧,用于左声道或右声道?

每个值(在这种情况下为 16 位整数)都用于单个通道。您永远不会有两个多字节值相互碰撞。

我希望这会有所帮助。我无法运行您的代码,但根据您的描述,我怀疑您遇到了字节序问题,并且您的样本不是真正的大字节序。

于 2015-08-20T22:08:10.800 回答
4

让我们从一些术语开始

  • 通道是样本的单声道流。该术语不一定意味着样本在数据流中是连续的。
  • 是一组同时发生的样本。对于立体声音频(例如 L 和 R 通道),一个帧包含两个样本。
  • 一个数据包是 1 个或多个帧,通常是系统一次可以处理的最小帧数。对于 PCM 音频,一个数据包通常包含 1 帧,但对于压缩音频,它会更大。
  • 交错是一个通常用于立体声音频的术语,其中数据流由连续的音频帧组成。因此,流看起来像 L1R1L2R2L3R3......LnRn

大端和小端音频格式都存在,并且取决于用例。但是,在系统之间交换数据时,这通常是一个问题——在处理或与操作系统音频组件交互时,您将始终使用本机字节顺序。

你没有说你使用的是小端系统还是大端系统,但我怀疑它可能是前者。在这种情况下,您需要对样本进行字节反转。

虽然不是一成不变的,但在使用浮点时,样本通常在范围内-1.0<x<+1.0,因此您希望将样本除以1<<15。当使用 16 位线性类型时,它们通常是有符号的。

处理字节交换和格式转换:

int s = (int) interleavedData[2 * i];
short revS = (short) (((s & 0xff) << 8) | ((s >> 8) & 0xff)) 
left[i] = ((float) revS) / 32767.0f;
于 2015-08-20T22:08:23.770 回答
2

实际上,您正在处理音频 CD 质量的几乎典型的 WAVE 文件,也就是说:

  • 2个频道
  • 44100 kHz 的采样率
  • 在 16 位有符号整数上量化的每个幅度样本

我说几乎是因为大端序通常用于 AIFF 文件(Mac 世界),而不是 WAVE 文件(PC 世界)。如果不搜索如何处理 Java 中的字节序,我不知道,所以我将这部分留给你。

关于样本的存储方式非常简单:

  • 每个样本占用 16 位(从 -32768 到 +32767 的整数)
  • 如果通道是交错的:(L,1),(R,1),(L,2),(R,2),...,(L,n),(R,n)
  • 如果通道不是:(L,1),(L,2),...,(L,n),(R,1),(R,2),...,(R,n)

然后提供音频回调,通常需要提供 32 位浮点,范围从 -1 到 +1。也许这就是您的 aglorithm 中可能缺少某些东西的地方。将您的整数除以 32768 (2^(16-1)) 应该可以使其听起来符合预期。

于 2015-08-20T22:07:20.210 回答
0

我遇到了一个类似的问题,short[] frames对通过 Spotify Android SDK 进来的去交错onAudioDataDelivered().

一年前的文档onAudioDelivered写得不好。请参阅 Github问题。他们用更好的描述和更准确的参数名称更新了文档:

onAudioDataDelivered(short[] samples, int sampleCount, int sampleRate, int channels)

令人困惑的是,它samples.length可能是 4096。但是,它仅包含sampleCount有效样本。如果您正在接收立体声音频,并且数组sampleCount = 2048中只有 1024 帧(每帧有两个样本)的音频!samples

因此,您需要更新您的实现以确保您正在使用sampleCount而不是samples.length.

于 2016-11-16T20:05:49.113 回答