交错立体声PCM线性Int16大端音频是什么样子?

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[]缓冲区的short数为2048
  • 每个short[]缓冲区的帧数为1024
  • 每个数据包的帧数为1

你的实现看起来几乎完美无误,尤其是当你说你能理解单词时,即使它们听起来不对。你使用的输出格式的详细信息是什么?我的猜测是,short-to-float转换需要进行比例和/或偏移量调整 - 使用float来指定范围[-32768, 32767]似乎有些奇怪。 - Sbodd
你是如何获取这个 short[] 数组的?如果样本已经是两个字节的整数,那么字节序不应该有影响。源是有符号还是无符号的?输出期望在什么范围内? - Piotr Praszmo
@Sbodd 是的,阅读答案后我认为缩放可能是问题所在。我正在努力实现一个标准化的过程。 - William Rosenbloom
@Banthar,这个短数组来自 Spotify Android SDK。这就是为什么我只能访问这些小块,因为我只有流媒体的权限。这些 short 类型数据是带符号的,在我的调试器中观察到的期望范围几乎涵盖了 -32768 到 32768 范围内的所有 short 类型数值。 - William Rosenbloom
4个回答

15
我理解我的音频有两个声道,因此样本将以[left] [right] [left] [right] ...的格式存储。但我不明白这意味着什么。 交错PCM数据是按通道顺序每个通道一个采样地存储,然后才进入下一个采样。 PCM帧由每个通道的一组采样组成。如果您具有左声道和右声道的立体声音频,则每个声道的一个样本一起形成一个帧。
  • Frame 0:[left sample] [right sample]
  • Frame 1:[left sample] [right sample]
  • Frame 2:[left sample] [right sample]
  • Frame 3:[left sample] [right sample]
  • 等等...
每个样本都是时间上瞬时点上压力的测量和数字量化。也就是说,如果每个样本有8位,那么可以在256个可能的精度级别上采样压力。由于声波是波动,具有峰值和谷值,我们要测量到中心的距离。因此,我们可以定义中心为127左右,并从那里减去和加上(0到255,无符号),或者我们可以将这些8位视为带符号的(相同的值,只是它们的解释不同),并从-128到127进行测量。 对于单声道(mono)音频,每个样本使用8位,因此每个采样需要一个字节,这意味着以44.1kHz采样的一秒音频正好使用44,100个字节的存储空间。 现在,让我们假设每个采样为8位,但是在44.1.kHz立体声下。每隔一个字节将用于左声道,每隔一个将用于右声道。
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位整数)都用于单个通道。你永远不会在一起压缩两个多字节值。

希望有所帮助。我无法运行您的代码,但根据您的描述,我怀疑您存在字节序问题,您的样本不是真正的大端序。


5
让我们首先了解一些术语:
- 通道是一组单声道采样。该术语不一定意味着采样在数据流中是连续的。 - 帧是一组完全相同的采样。对于立体声音频(例如L和R通道),一帧包含两个采样。 - 包是1个或多个帧,并且通常是系统一次处理的最小帧数。对于PCM音频,一个包通常包含1个帧,但对于压缩音频,它将更大。 - 交错是一个通常用于立体声音频的术语,其中数据流由连续的音频帧组成。因此,流看起来像L1R1L2R2L3R3......LnRn。
存在大端和小端音频格式,并取决于使用情况。然而,在系统之间交换数据时通常不会出现问题 - 您将始终在处理或与操作系统音频组件进行接口时使用本机字节顺序。
您没有说明使用的是小端还是大端系统,但我认为可能是前者。在这种情况下,您需要反转采样的字节顺序。
虽然没有确切规定,但使用浮点采样时通常在范围内-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;

有趣的是你通过 32767.0f 进行了归一化。@maxime.bochon 建议我应该除以 32768。我感觉我也听说过对于多通道音频缓冲区,音量应该进一步除以通道数。如果不进行归一化,音频会听起来怎样? - William Rosenbloom
这在很大程度上取决于是否认为1.0f的值被剪裁了。使用1<<15进行归一化计算肯定比除法更便宜(除法是位移)。至于缺乏归一化:在信号链中没有任何影响,直到你遇到音频硬件,如DAC。此时,您的信号将在两个方向上被严重剪裁。 - marko

3
实际上,你正在处理一个几乎标准的音频CD质量的WAVE文件,也就是说:
- 2个声道 - 采样率为44100kHz - 每个幅度样本量化为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来馈送音频回调通常是必需的。也许这就是你的算法中可能缺少的地方。将你的整数除以32768(2 ^(16-1))应该会听起来像期望的那样。

说实话,根据这些信息,我认为我的数据是小端的,这可能是我的问题之一。这是一个漫长的故事,但我以为我的数据是大端的,因为我使用苹果的音频转换服务在iPhone上测试了来自同一发件人的音频。我需要大端数据才能到达目的地。我也相信规范化数据会有所帮助,现在正在努力实现它。 - William Rosenbloom

0

我在使用Spotify Android SDK的onAudioDataDelivered()时遇到了类似的问题,需要对short[] frames进行去交错处理。

一年前,onAudioDelivered的文档写得很糟糕。可以参考Github issue。现在他们已经更新了文档,并提供了更好的描述和更准确的参数名称:

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接