【问题标题】:Correct WAVE_FORMAT_1S16 PCM dual-channel format?正确的 WAVE_FORMAT_1S16 PCM 双通道格式?
【发布时间】:2021-02-24 21:21:58
【问题描述】:

我正在尝试在 Windows 10 WinMM 音频上播放 2 个 16 位 PCM 流, 每个都在一个单独的通道上,使用这个 WAVEFORMATEX :

const WAVEFORMATEX
  _wex_  = // (WAVEFORMATEX)
 { .wFormatTag     = WAVE_FORMAT_PCM
 , .nChannels      = 2
 , .nSamplesPerSec = 8000
 , .nAvgBytesPerSec= 16000
 , .nBlockAlign    = 4
 , .wBitsPerSample = 16
 , .cbSize         = 0
 };

当我使用 2 个进程播放它们时,进程 A 将其流布局为:

   Bit 31:              |                 :Bit 0
          <PCM_16_BITS> |    00   ...    00

进程 B 将其流布局如下:

   Bit 31:              |                 :Bit 0
          00  ...   00  |    <PCM_16_BITS>

,则流在左右声道播放成功(是Mixed 由 WAS Mixer 设备 - 每个流仅在一个通道上播放,因此仅播放 左或右扬声器)。

但是,如果我编写一个结合两个流的单个进程,那么 单个流的布局如下:

   Bit 31:                   |                     :Bit 0
          <LEFT PCM_16_BITS> | <RIGHT PCM_16_BITS>

然后流播放为乱码废话。

请任何人告诉我 Windows 音频的实际字节布局 期望帧具有由我的 WAVEFORMATEX 配置的 2 通道 16 位 PCM?

我已经编写了调用waveOutOpen、waveOutPrepareHeader和waveOutWrite等的代码,效果很好,只是当我尝试 播放具有 2 个左|右 16 位音频样本的组合流 输出乱码的32位字的高16位和低16位- 我想我只是不知道 Windows 音频在这里期待什么格式。

或者我的 WAVEFORMATEX 有什么错误?

或者指出这可能记录在哪里? Microsoft 文档详细介绍了头文件内容,但根本没有真正解释流布局等内容。

提前感谢您提供任何有用的建议/回复。

【问题讨论】:

  • 如果 WAS 有类似于 ALSA (alsa-project.org/wiki/FramesPeriods) 的东西就好了,它声明:“假设我们想要使用立体声、16 位、44.1 KHz 流,一个-方式(意思是,在播放或捕捉方向)。然后我们有: '立体声' = 通道数:2 1 个模拟样本用 16 位 = 2 个字节表示 1 帧表示来自所有通道的 1 个模拟样本;这里我们有2 个通道,依此类推:1 帧 = (num_channels) *(1 个样本,以字节为单位)=(2 个通道)*(每个样本 2 个字节(16 位))= 4 个字节(32 位)"
  • 所以 ALSA 文档建议左 | 右 16 位 PCM 应该像我正在做的那样以 32 位字布局 & 如上所示,但 WAS 不喜欢这样 - 我有吗使用 8 个字节,在 2 个 32 位字的 2 个低 16 位中有 2 个 16 位字?

标签: audio windows-10


【解决方案1】:

啊哈!我终于找到了:

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ksmedia/ns-ksmedia-waveformatextensible

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ksmedia/ns-ksmedia-ksaudio_channel_config

确实回答了这个问题 - 我想如果我发送 2 个频道 在一帧中,我必须为每个通道使用 12 个字节。

【讨论】:

    【解决方案2】:

    好的,最后一个答案并不是真正的答案 - 这是一个更好的尝试 -

    为了让其他人免于我过去几天的头痛, 这是更正后的“pwfx”waveOutOpen 参数:

    const WAVEFORMATEXTENSIBLE
      _wex_ext_ =
      { .Format =
        { .wFormatTag     = WAVE_FORMAT_EXTENSIBLE
        , .nChannels      = 2
        , .nSamplesPerSec = 8000
        , .nAvgBytesPerSec= 32000
        , .nBlockAlign    = 4
        , .wBitsPerSample = 16
        , .cbSize         = sizeof(WAVEFORMATEXTENSIBLE) - 
                          sizeof(WAVEFORMATEX)
       }
     , .Samples       = {16}
     , .dwChannelMask = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
     , .SubFormat = DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_PCM)
     };
    

    因此,如果将其用作 'pwfx' 指向的结构:

      switch
      ( r =
        waveOutOpen
        ( &aoh        // Audio Output Handle
        , WAVE_MAPPER
        , ((WAVEFORMATEX*)&_wex_ext_) 
        , ((DWORD_PTR)waveOutProc) // Callback function on WOM_DONE
        , ((DWORD_PTR)0UL)         // no user paramet
        , WAVE_FORMAT_DIRECT | CALLBACK_FUNCTION // do not modify audio data
        )
      )
      {case MMSYSERR_NOERROR:
        break;
       case MMSYSERR_ALLOCATED:
        ok   = false;
        emsg = "Specified resource is already allocated.";
        break;
       case MMSYSERR_BADDEVICEID:
        ok   = false;
        emsg = "Specified device identifier is out of range.";
        break;
       case MMSYSERR_NODRIVER:
        ok   = false;
        emsg = "No device driver is present.";
        break;
       case MMSYSERR_NOMEM:
        ok   = false;
        emsg = "Unable to allocate or lock memory.";
        break;
       case WAVERR_BADFORMAT:
        ok   = false;
        emsg = "Attempted to open with an unsupported waveform-audio format.";
        break;
       case WAVERR_SYNC:
        ok   = false;
        emsg = "The device is synchronous but waveOutOpen was called without using the WAVE_ALLOWSYNC flag.";
        break;
       default:
        ok = false;
        if ( 0 == waveOutGetErrorText(r, ((u8_t*) &pcm[0].l[0]), 1024 ))
        { u16_t len = 1024;
          s8_t *str = ((s8_t*)&pcm[0].l[0]);
          utf8str( ((u16_t*)&pcm[0].l[0]), wcslen((U16_t*)&pcm[0].l[0]), &str, &len);
          emsg = ((const char*) &pcm[0].l[0]);
        }
        break;
      }
    

    那么2个进程播放的两种SINGLE CHANNEL格式之一:

                       Bit:15
     Bit: 31:               |                    Bit: 0:
           <16-bit LE PCM L>| 0           ...         0   # all zeros, OR:
    
                       Bit:15
     Bit: 31:               |                    Bit: 0:
           0      ...      0|       <16-bit LE PCM R>  | 
      
         
    

    工作正常,混合后“PCM L”在左声道播放,PCM R 在右频道播放,

    但我没有成功使用单个进程玩这个:

                                   Bit:15
     Bit: 31:                           |                    Bit:    0:
          <1|       <16-bit LE PCM L>  | <16-bit LE PCM R> 
    

    在 Linux 上使用 ALSA (alsa-lib) 播放流时也会发生同样的情况 - 所以它不是 Windows 的东西。

    在 Linux 上,使用 ALSA 的 S16_LE 格式, 我曾尝试在一个过程中播放相同的流,例如:

                        Bit:15
      Bit: 31:               |                    Bit: 0:
            <16-bit LE PCM L>| 0           ...         0   # all zeros 
            0      ...      0|       <16-bit LE PCM R>  
    

    我也试过了:

                        Bit:15
      Bit: 31:               |               Bit: 0:
            0      ...      0|    <16-bit LE PCM L>|   
            0      ...      0|    <16-bit LE PCM R>| 
    

    和:

                        Bit:15
      Bit: 31:               |               Bit: 0:
            <16-bit LE PCM L>|   0       ...      0| 
            <16-bit LE PCM R>|   0       ...      0|     
       
    

    但是这些也不起作用(流中充满了点击和故障 并且信号似乎在左右交替之间几乎没有 听得见)。当然,如果我指定了 S16_LE 格式,需要 16 位帧,我正在写 32 位 帧?

    所以,我真的不知道确切的交错字节格式是什么 应该是。

    在 Windows 上,执行交错的代码如下所示:

      typedef struct pcm_frame_s
      { U16_t l[320], r[320], lr[640];
      } PCMFrame_t;
    
      static
        PCMFrame_t pcm[16] = {0};
      
    
      ...
      // pcmA & pcmB are pointers to pcm[i].l[0] & pcm[i].r[0] :
      if ( pcmA && pcmB )
         { register U8_t
            np;
          register U16_t
            *ppcm  =&pcm[i].lr[0];
          for( np = 0
             , pcmA=&pcm[i].l[0]
             , pcmB=&pcm[i].r[0]
             ; np < 160
             ; ++np, ++ppcm, ++pcmA, ++pcmB
             )
          { *ppcm   = *pcmA ? *pcmA : pcm_slnc[np];
            ppcm   += 1;
            *ppcm   = *pcmB ? *pcmB : pcm_slnc[np];
          } // pcm_slnc is a pre-computed "Comfort Noise" block
        
        }else
        { register U8_t
            np;
          register U16_t
            *ppcm  = &pcm[i].lr[0]
          , *pspcm = pcmA ? pcmA : pcmB;
          if(left) // user has chosen to put 1st stream on left channel
          {
            for(np=0; np < 160; ++np, ++ppcm, ++pspcm)
            { *ppcm = *pspcm;
              ppcm += 1;
              *ppcm = pcm_slnc[np];
            }    
          } else
          {
            for(np=0; np < 160; ++np, ++ppcm, ++pspcm)
            { *ppcm = pcm_slnc[np];
              ppcm += 1;
              *ppcm=*pspcm;
            }
          }
        }            
        while (  (WHDR_PREPARED | WHDR_DONE) 
              != (wh[i].dwFlags & (WHDR_PREPARED | WHDR_DONE)) 
              )
        { if (!WaitOnAddress
               ( &(wh[i].dwFlags)
               , &playingDwFlags
               , sizeof(u32_t)
               , INFINITE
               )
             )
          { say(FMT("WaitOnAddress failed in INFINITE mode."));
            ok = false;
            break;
          }
        }
        wh[i].dwFlags &= ~WHDR_DONE;
        switch
        ( r = waveOutWrite(aoh, &wh[i], sizeof(wh[i])) )
        {case MMSYSERR_NOERROR:
        ...
    

    我认为我必须指定每个通道 32 位,并使用 32 位帧大小?

    所以我无法将两个 16 位左交错 |右声道
    PCM 值彼此相邻而不使用 32 位流格式?

    我即将放弃,只使用 2 个进程和 混合器,奇怪的是它尊重“|0”(左进程)和 "0|"(右处理)(左|右)通道格式 并按预期将它们发送到左/右扬声器。

    但是有没有办法在一个 32 位字中指定两个 16 位通道? 奇怪。

    【讨论】:

      猜你喜欢
      • 2015-10-16
      • 2014-11-20
      • 1970-01-01
      • 2015-11-28
      • 1970-01-01
      • 2018-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多