PCM转WAV

作者: coder_feng | 来源:发表于2021-05-11 10:21 被阅读0次

    录制PCM之后,我在命令行播放成功之后,也用了代码去播放,最终也都是成功的,然后我就想能否直接用播放器直接播放呢?
    我尝试了一下,结果发现并不成功,经过资料收集,发现播放器是无法播放原始数据的,因为播放器是不知道PCM的采样率、声道数、位深度等参数的,所以需要用到前面音频基础学到的知识来实现这种效果,所以这里打算使用PCM,转化成其他文件格式。例如WAV等

    1.WAV文件格式

    在PCM需要转WAV文件格式之前,我们先要了解WAV文件格式1,文件格式2
    WAV 文件格式来源于微软Microsoft,遵循RIFF标准的文件格式,每个块由块标识符,块长度和块数据组成,官方解释

    Wave files have a master RIFF chunk which includes a WAVE identifier followed by sub-chunks. The data is stored in little-endian byte order.
    
    WavFormat.png
    fmt Chunk.png data Chunk.png

    通过WAV介绍的链接,截取其中的相关图片,结合分析,可以得出一个通俗易懂的完整图片,如下图所示:


    WAV.png

    参数解释

    每一chunk数据块包含3部分,前面也提及到
    
    • ckID: 占4个字节,chunk的标识
    • chsize: chunk的数据部分大小,占用4+n个字节,后面n个字节为data size
    • data: chunk的数据部分
      整一个WAV chunks文件由3部分组成,WAVEID,fmt chunk,data chunk
    • WAVEID: 文件类型
    • fmt chunk
    音频参数相关的chunk,包含采样率、声道数、位深度等参数信息
    
    • data chunk
    音频数据相关的chunk,包含真正的音频数据,比如PCM数据
    

    2. 命令操作

    songlin@feng-sl  ~/audio/pcm_to_wav   master ±  ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm out.wav
    ffmpeg version 4.3.2 Copyright (c) 2000-2021 the FFmpeg developers
      built with Apple clang version 12.0.0 (clang-1200.0.32.29)
      configuration: --prefix=/usr/local/Cellar/ffmpeg/4.3.2_4 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox
      libavutil      56. 51.100 / 56. 51.100
      libavcodec     58. 91.100 / 58. 91.100
      libavformat    58. 45.100 / 58. 45.100
      libavdevice    58. 10.100 / 58. 10.100
      libavfilter     7. 85.100 /  7. 85.100
      libavresample   4.  0.  0 /  4.  0.  0
      libswscale      5.  7.100 /  5.  7.100
      libswresample   3.  7.100 /  3.  7.100
      libpostproc    55.  7.100 / 55.  7.100
    [s16le @ 0x7fa6a8008200] Estimating duration from bitrate, this may be inaccurate
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, s16le, from 'out.pcm':
      Duration: 00:00:51.94, bitrate: 1411 kb/s
        Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
    Stream mapping:
      Stream #0:0 -> #0:0 (pcm_s16le (native) -> pcm_s16le (native))
    Press [q] to stop, [?] for help
    Output #0, wav, to 'out.wav':
      Metadata:
        ISFT            : Lavf58.45.100
        Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, stereo, s16, 1411 kb/s
        Metadata:
          encoder         : Lavc58.91.100 pcm_s16le
    size=    8948kB time=00:00:51.94 bitrate=1411.2kbits/s speed= 389x
    video:0kB audio:8948kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000851%
    

    这里使用s16le 的格式,是因为我的pcm音频是这个s161e格式的,所以需要使用相同的格式,要是使用f32le 这个格式去转换,得出来的数据大小就会相差很远,这个是不正确的

     Stream #0:0 -> #0:0 (pcm_s16le (native) -> pcm_s16le (native))
    

    这个就代表转换的格式是没有变化的。
    然后查看大小有没有变化

    wav-pcm.png
    从图上面可以看出来WAV的字节大小对比PCM的字节大小大了78个字节,从我们前面学到的只是来看,应该是44个字节才对,那么剩下的34个字节是什么东西么,我们来看看文件的二进制文件:
    WAV包含list trunk.png ,发现34个字节是一个list trunk的东西,这个list trunk是什么东西呢,具体可以参考
    What is a “LIST” chunk in a RIFF/Wav header?
    或者
    List chunk(of a RIFF file)
    另外我们也可以通过加上一个输出文件参数-bitexact 可以去掉List Chunk
    ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm -bitexact out1.wav
    
    WAC-PCM-NOList.png

    从图结果可以看到,完美去除list trunk

    3.代码编程

    结合之前的录音例子,这里最重要的事情就是补充WAV头文件即可,所以关键代码如下

    wavheader.h

    // WAV文件头(44字节)
    typedef struct {
        // RIFF chunk的id
        uint8_t riffChunkId[4] = {'R', 'I', 'F', 'F'};
        // RIFF chunk的data大小,即文件总长度减去8字节
        uint32_t riffChunkDataSize;
    
        // "WAVE"
        uint8_t format[4] = {'W', 'A', 'V', 'E'};
    
        /* fmt chunk */
        // fmt chunk的id
        uint8_t fmtChunkId[4] = {'f', 'm', 't', ' '};
        // fmt chunk的data大小:存储PCM数据时,是16
        uint32_t fmtChunkDataSize = 16;
        // 音频编码,1表示PCM,3表示Floating Point
        uint16_t audioFormat = AUDIO_FORMAT_PCM;
        // 声道数
        uint16_t numChannels;
        // 采样率
        uint32_t sampleRate;
        // 字节率 = sampleRate * blockAlign
        uint32_t byteRate;
        // 一个样本的字节数 = bitsPerSample * numChannels >> 3
        uint16_t blockAlign;
        // 位深度
        uint16_t bitsPerSample;
    
        /* data chunk */
        // data chunk的id
        uint8_t dataChunkId[4] = {'d', 'a', 't', 'a'};
        // data chunk的data大小:音频数据的总长度,即文件总长度减去文件头的长度(一般是44)
        uint32_t dataChunkDataSize;
    } WAVHeader;
    

    wavheader.cpp

    void FFmpegs::pcm2wav(WAVHeader &header,
                          const char *pcmFilename,
                          const char *wavFilename) {
        header.blockAlign = header.bitsPerSample * header.numChannels >> 3;
        header.byteRate = header.sampleRate * header.blockAlign;
    
        // 打开pcm文件
        QFile pcmFile(pcmFilename);
        if (!pcmFile.open(QFile::ReadOnly)) {
            qDebug() << "文件打开失败" << pcmFilename;
            return;
        }
        header.dataChunkDataSize = pcmFile.size();
        header.riffChunkDataSize = header.dataChunkDataSize
                                   + sizeof (WAVHeader)
                                   - sizeof (header.riffChunkId)
                                   - sizeof (header.riffChunkDataSize);
    
        // 打开wav文件
        QFile wavFile(wavFilename);
        if (!wavFile.open(QFile::WriteOnly)) {
            qDebug() << "文件打开失败" << wavFilename;
    
            pcmFile.close();
            return;
        }
    
        // 写入头部
        wavFile.write((const char *) &header, sizeof (WAVHeader));
    
        // 写入pcm数据
        char buf[1024];
        int size;
        while ((size = pcmFile.read(buf, sizeof (buf))) > 0) {
            wavFile.write(buf, size);
        }
    
        // 关闭文件
        pcmFile.close();
        wavFile.close();
    }
    
    3.1函数调用
    // 获取输入流
        AVStream *stream = ctx->streams[0];
        // 获取音频参数
        AVCodecParameters *params = stream->codecpar;
    
        // pcm转wav文件
        WAVHeader header;
        header.sampleRate = params->sample_rate;
        header.bitsPerSample = av_get_bits_per_sample(params->codec_id);
        header.numChannels = params->channels;
        if (params->codec_id >= AV_CODEC_ID_PCM_F32BE) {
            header.audioFormat = AUDIO_FORMAT_FLOAT;
        }
        FFmpegs::pcm2wav(header,
                         filename.toUtf8().data(),
                         wavFilename.toUtf8().data());
    

    debug运行程序之后就发现在保存的目录中有两个文件,一个是pcm,一个是wav,用播放器播放wav,发现是可以成功播放的!

    相关文章

      网友评论

          本文标题:PCM转WAV

          本文链接:https://www.haomeiwen.com/subject/lnaddltx.html