音频demo:将PCM数据和ogg vorbis格式相互编解码

1、README

a. libogg+libvorbis移植步骤

源码下载地址:https://xiph.org/downloads/

tar xzf libogg-1.3.5.tar.gz
cd libogg-1.3.5/
#./configure --prefix=$PWD/_install --disable-shared --enable-static --with-pic
./configure --prefix=$PWD/_install
make -j8
make install
strip  --strip-unneeded _install/lib/*

cd ../
tar xzf libvorbis-1.3.7.tar.gz 
cd libvorbis-1.3.7/
./configure --prefix=$PWD/_install --with-ogg=$PWD/../libogg-1.3.5/_install/
make -j8
make install
strip  --strip-unneeded _install/lib/*
$ tree libogg-1.3.5/_install/lib/ libogg-1.3.5/_install/include/ -h
libogg-1.3.5/_install/lib/
├── [ 31K]  libogg.a
├── [ 990]  libogg.la
├── [  15]  libogg.so -> libogg.so.0.8.5
├── [  15]  libogg.so.0 -> libogg.so.0.8.5
├── [ 34K]  libogg.so.0.8.5
└── [4.0K]  pkgconfig
    └── [ 340]  ogg.pc
libogg-1.3.5/_install/include/
└── [4.0K]  ogg
    ├── [ 538]  config_types.h
    ├── [8.2K]  ogg.h
    └── [4.7K]  os_types.h

2 directories, 9 files


$ tree libvorbis-1.3.7/_install/lib/ libvorbis-1.3.7/_install/include/ -h
libvorbis-1.3.7/_install/lib/
├── [292K]  libvorbis.a
├── [691K]  libvorbisenc.a
├── [1.3K]  libvorbisenc.la
├── [  22]  libvorbisenc.so -> libvorbisenc.so.2.0.12
├── [  22]  libvorbisenc.so.2 -> libvorbisenc.so.2.0.12
├── [690K]  libvorbisenc.so.2.0.12
├── [ 46K]  libvorbisfile.a
├── [1.3K]  libvorbisfile.la
├── [  22]  libvorbisfile.so -> libvorbisfile.so.3.3.8
├── [  22]  libvorbisfile.so.3 -> libvorbisfile.so.3.3.8
├── [ 38K]  libvorbisfile.so.3.3.8
├── [1.2K]  libvorbis.la
├── [  18]  libvorbis.so -> libvorbis.so.0.4.9
├── [  18]  libvorbis.so.0 -> libvorbis.so.0.4.9
├── [227K]  libvorbis.so.0.4.9
└── [4.0K]  pkgconfig
    ├── [ 446]  vorbisenc.pc
    ├── [ 472]  vorbisfile.pc
    └── [ 385]  vorbis.pc
libvorbis-1.3.7/_install/include/
└── [4.0K]  vorbis
    ├── [8.2K]  codec.h
    ├── [ 17K]  vorbisenc.h
    └── [7.8K]  vorbisfile.h

2 directories, 21 files
b. 说明
demo现状
  • 参考libvorbis-1.3.7/examples/目录下实现的代码;

  • blog_readOggPage.c是在参考文章中的代码基础上调整的;

  • main_pcm_2_ogg_vorbis.cmain_ogg_vorbis_2_pcm.c可以正常编解码,并且能正常播放;

关于verbois格式的说明
  • Vorbis是一种有损音讯压缩格式,由Xiph.Org基金会所领导并开放源代码的一个免费的开源软件项目。
  • Vorbis通常以Ogg作为容器格式,所以常合称为Ogg Vorbis。
  • 目前Xiph.Org基金会建议使用延迟更低、音质更好的Opus编码来取代Vorbis。
  • 32 kb/秒(-q-2)到500 kb/秒(-q10)的比特率。
  • 采样率从8 kHz(窄带)到192 kHz(超频)。
  • 支持采样精度 16bit\20bit\24bit\32bit。
  • 采用可变比特率(VBR),动态调整比特率达到最佳的编码效果。
  • 支持单声道、立体声、四声道和5.1环绕声道;支持多达255个音轨(多数据流的帧)。
  • 可动态调节比特率,音频带宽和帧大小。
  • Vorbis使用了一种灵活的格式,能够在文件格式已经固定下来后还能对音质进行明显的调节和新算法调校。
  • 可以封装在多种媒体容器格式中,如Ogg( .oga)、Matroska( .mka)、WebM( .webm)等。
c. 使用方法
编译
make clean && make
编码
$ ./pcm_2_ogg_vorbis 
Usage: 
         ./pcm_2_ogg_vorbis <in-pcm-file> <sample-rate> <channels> <samples once> <quality(-0.1~1.0)> <out-ogg-file>
examples: 
         ./pcm_2_ogg_vorbis ./audio/test_8000_16_1.pcm  8000 1 160 0.8 out1.ogg
         ./pcm_2_ogg_vorbis ./audio/test_44100_16_2.pcm 44100 2 1024 0.5 out2.ogg
解码
$ ./ogg_vorbis_2_pcm 
Usage: 
         ./ogg_vorbis_2_pcm <in-ogg-vorbis-file> <out-pcm-file>
examples: 
         ./ogg_vorbis_2_pcm ./audio/out1.ogg out1.pcm
         ./ogg_vorbis_2_pcm ./audio/out2.ogg out2.pcm
d. 参考文章
e. demo目录架构
$ tree
.
├── audio
│   ├── out1.ogg
│   ├── out2.ogg
│   ├── test_44100_16_2.pcm
│   └── test_8000_16_1.pcm
├── avfile
├── blog_readOggPage.c
├── docs
│   ├── Vorbis - 求闻百科,共笔求闻.mhtml
│   ├── 【音视频 _ Ogg】libogg库详细介绍以及使用——附带libogg库解析.opus文件的C源码-CSDN博客.mhtml
│   └── 【音视频 _ Ogg】Ogg封装格式详解——包含Ogg封装过程、数据包(packet)、页(page)、段(segment)等-CSDN博客.mhtml
├── include
│   ├── ogg
│   │   ├── config_types.h
│   │   ├── ogg.h
│   │   └── os_types.h
│   └── vorbis
│       ├── codec.h
│       ├── vorbisenc.h
│       └── vorbisfile.h
├── lib
│   ├── libogg.a
│   ├── libvorbis.a
│   ├── libvorbisenc.a
│   └── libvorbisfile.a
├── main_ogg_vorbis_2_pcm.c
├── main_pcm_2_ogg_vorbis.c
├── Makefile
├── opensource
│   └── libvorbis-1.3.7.tar.gz
├── README.md
├── reference_code
│   ├── chaining_example.c
│   ├── decoder_example.c
│   ├── encoder_example.c
│   ├── seeking_example.c
│   └── vorbisfile_example.c
└── tools

10 directories, 28 files

2、主要代码片段

main_pcm_2_ogg_vorbis.c
#include <stdio.h>
#include <stdlib.h>

#include "vorbis/vorbisenc.h"


int main(int argc, char **argv)
{
    ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
    ogg_page         og; /* one Ogg bitstream page.  Vorbis packets are inside */
    ogg_packet       op; /* one raw packet of data for decode */
    vorbis_info      vi; /* struct that stores all the static vorbis bitstream settings */
    vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
    vorbis_block     vb; /* local working space for packet->PCM decode */
    int eos=0,ret;

    /* 检查参数 */
    if(argc != 7)
    {
        printf("Usage: \n"
                "\t %s <in-pcm-file> <sample-rate> <channels> <samples once> <quality(-0.1~1.0)> <out-ogg-file>\n"
                "examples: \n"
                "\t %s ./audio/test_8000_16_1.pcm  8000 1 160 0.8 out1.ogg\n"
                "\t %s ./audio/test_44100_16_2.pcm 44100 2 1024 0.5 out2.ogg\n"
                , argv[0], argv[0], argv[0]);
        return -1;
    }
    char *in_pcm_file_name = argv[1];
    unsigned int sample_rate = atoi(argv[2]);
    unsigned int channels = atoi(argv[3]);
    unsigned int samples_cnt = atoi(argv[4]);
    float quality = strtof(argv[5], NULL);
    char *out_ogg_file_name = argv[6];
    FILE *fp_in_pcm = NULL;
    FILE *fp_out_ogg = NULL;
    char *readbuffer = NULL;

    /* 文件操作 */
    fp_in_pcm = fopen(in_pcm_file_name, "rb");
    if (!fp_in_pcm) {
        printf("Error opening input file: %s\n", in_pcm_file_name);
        return -1;
    }
    fp_out_ogg = fopen(out_ogg_file_name, "wb");
    if (!fp_out_ogg) {
        printf("Error opening output file\n");
        fclose(fp_in_pcm);
        return -1;
    }
    readbuffer = malloc(samples_cnt * channels * sizeof(short));
    if (!readbuffer) {
        printf("alloc memory failed.\n");
        fclose(fp_in_pcm);
        fclose(fp_out_ogg);
        return -1;
    }

    vorbis_info_init(&vi);
    // 初始化,支持平均码率和可变码率
    //ret = vorbis_encode_init(&vi, 2, 44100, -1, 128000, -1);
    ret = vorbis_encode_init_vbr(&vi, channels, sample_rate, quality);
    if(ret)
    {
        printf("vorbis_encode_init_vbr failed with %d\n", ret);
        return -1;
    }

    /* set up the analysis state and auxiliary encoding storage */
    vorbis_analysis_init(&vd, &vi);
    vorbis_block_init(&vd, &vb);

    /* 流初始化,需要用到“逻辑比特流唯一序列号” */
    ogg_stream_init(&os, 0x12345678);

    // 添加头部:Vorbis流以3个头部开始,编解码参数(ogg比特流特性)+注释+比特流码本
    {
        ogg_packet header;
        ogg_packet header_comm;
        ogg_packet header_code;

        /* 添加注释 */
        vorbis_comment   vc;
        vorbis_comment_init(&vc);
        vorbis_comment_add_tag(&vc, "ENCODER", argv[0]);

        vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);
        ogg_stream_packetin(&os, &header); /* automatically placed in its own page */
        ogg_stream_packetin(&os, &header_comm);
        ogg_stream_packetin(&os, &header_code);

        /* This ensures the actual audio data will start on a new page, as per spec */
        while(!eos){
            int result = ogg_stream_flush(&os, &og);
            if(result == 0)
                break;
            fwrite(og.header, 1, og.header_len, fp_out_ogg);
            fwrite(og.body, 1, og.body_len, fp_out_ogg);
        }

        vorbis_comment_clear(&vc);
    }

    while(!eos)
    {
        long i = 0;
        long bytes = fread(readbuffer, 1, samples_cnt*channels*sizeof(short), fp_in_pcm);
        if(bytes == 0)
        {
            /* 文件结束 */
        }
        else
        {
            /* 分配数据空间 */
            float **buffer = vorbis_analysis_buffer(&vd, samples_cnt);
            for(i = 0; i < bytes/channels/sizeof(short); i++)
            {
                buffer[0][i] = ((readbuffer[i*channels*sizeof(short)+1]<<8)|(0x00ff&(int)readbuffer[i*channels*sizeof(short)])) / 32768.f;
                if(channels == 2)
                {
                    buffer[1][i] = ((readbuffer[i*channels*sizeof(short)+3]<<8)|(0x00ff&(int)readbuffer[i*channels*sizeof(short)+2])) / 32768.f;
                }
                else if(channels == 3) // 更多声道数据填充
                {
                }
            }
        }

        /* 告知提交了多少数据 */
        vorbis_analysis_wrote(&vd, i);

        while(vorbis_analysis_blockout(&vd, &vb) == 1) /* 数据预分析,分块,一个一个块进行编码 */
        {
            /* 数据分析 */
            vorbis_analysis(&vb, NULL);
            vorbis_bitrate_addblock(&vb);

            while(vorbis_bitrate_flushpacket(&vd, &op))
            {
                /* 将数据包"焊接"到比特流中 */
                ogg_stream_packetin(&os, &op);

                /* 得到ogg page页面数据 */
                while(!eos)
                {
                    int result = ogg_stream_pageout(&os, &og);
                    if(result == 0)
                        break;
                    fwrite(og.header, 1, og.header_len, fp_out_ogg);
                    fwrite(og.body, 1, og.body_len, fp_out_ogg);

                    /* 流结束 */
                    if(ogg_page_eos(&og))
                        eos = 1;
                }
            }
        }
    }

    ogg_stream_clear(&os);
    vorbis_block_clear(&vb);
    vorbis_dsp_clear(&vd);
    vorbis_info_clear(&vi);

    free(readbuffer);
    fclose(fp_in_pcm);
    fclose(fp_out_ogg);

    printf("PCM -> Ogg Vorbis Success.\n");

    return 0;
}
main_ogg_vorbis_2_pcm.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "vorbis/codec.h"


#define BUF_SIZE    (4096)
ogg_int16_t convbuffer[BUF_SIZE];

int main(int argc, char **argv)
{
    ogg_sync_state   oy; /* sync and verify incoming physical bitstream */
    ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
    ogg_page         og; /* one Ogg bitstream page. Vorbis packets are inside */
    ogg_packet       op; /* one raw packet of data for decode */
    vorbis_info      vi; /* struct that stores all the static vorbis bitstream settings */
    vorbis_comment   vc; /* struct that stores all the bitstream user comments */
    vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
    vorbis_block     vb; /* local working space for packet->PCM decode */
    char *buffer = NULL;
    int  bytes = 0;
    int  ret = 0;
    int convsize = 0;
    int total_read_bytes = 0;

    /* 检查参数 */
    if(argc != 3)
    {
        printf("Usage: \n"
                "\t %s <in-ogg-vorbis-file> <out-pcm-file>\n"
                "examples: \n"
                "\t %s ./audio/out1.ogg out1.pcm\n"
                "\t %s ./audio/out2.ogg out2.pcm\n"
                , argv[0], argv[0], argv[0]);
        return -1;
    }
    char *in_ogg_vorbis_file_name = argv[1];
    char *out_pcm_file_name = argv[2];
    FILE *fp_in_ov = NULL;
    FILE *fp_out_pcm = NULL;

    /* 文件操作 */
    fp_in_ov = fopen(in_ogg_vorbis_file_name, "rb");
    if (!fp_in_ov) {
        printf("Error opening input file: %s\n", in_ogg_vorbis_file_name);
        ret = -1;
        goto exit;
    }
    fp_out_pcm = fopen(out_pcm_file_name, "wb");
    if (!fp_out_pcm) {
        printf("Error opening output file\n");
        ret = -1;
        goto exit;
    }

    /* 初始化,接下来就可以读取ogg page了 */
    ogg_sync_init(&oy);

    while(1) // 如果只有一路逻辑流,可以不需要这个while循环
    {
        /* 目的通过第一页拿到逻辑比特流的"唯一序列号"bitstream_serial_number, 4096个字节一般足够了 */
        /* 填充数据 */
        buffer = ogg_sync_buffer(&oy, BUF_SIZE);
        bytes = fread(buffer, 1, BUF_SIZE, fp_in_ov);
        ogg_sync_wrote(&oy, bytes);
        total_read_bytes += bytes;

        vorbis_info_init(&vi);
        vorbis_comment_init(&vc);

        /* 获取vorbis的3个头部 */
        int i = 0;
        while(i < 3)
        {
            int result = 1;
            do{
                if(bytes != BUF_SIZE || (result = ogg_sync_pageout(&oy, &og)) != 1)
                {
                    fprintf(stderr,"Input does not appear to be an Ogg bitstream.\n");
                    exit(1);
                }

                if(result == 1) // 1-成功 0-数据不够 <0-出错
                {
                    if(i == 0)
                    {
                        /* 获取逻辑比特流的"唯一序列号"bitstream_serial_number */
                        ogg_stream_init(&os, ogg_page_serialno(&og));
                    }

                    // ogg_stream_pagein 不管返回值,有问题的话 ogg_stream_packetout 会报错
                    ogg_stream_pagein(&os, &og);
                    result = ogg_stream_packetout(&os, &op);
                    if(result == 1)
                    {
                        result = vorbis_synthesis_headerin(&vi, &vc, &op);
                        if(result < 0)
                        {
                            fprintf(stderr,"Corrupt header.  Exiting.\n");
                            exit(1);
                        }
                    }
                    else if(result < 0)
                    {
                        fprintf(stderr,"Corrupt header.  Exiting.\n");
                        exit(1);
                    }
                    else if(result == 0)
                    {
                        break; /* 数据不够,填充 */
                    }
                }
                else if(result == 0)
                {
                    break; /* 数据不够,填充 */
                }

                /* 继续下一个header */
                i++;
                break;
            }while(0);

            /* 数据不够出1个page,继续填充数据 */
            if(result == 0)
            {
                buffer = ogg_sync_buffer(&oy, BUF_SIZE);
                bytes = fread(buffer, 1, BUF_SIZE, fp_in_ov);
                ogg_sync_wrote(&oy, bytes);
                total_read_bytes += bytes;
            }
        }

        /* 解析完3个header了,可以选择吧信息打印一下 */
        {
            char **ptr=vc.user_comments;
            while(*ptr){
                fprintf(stdout,"%s\n",*ptr);
                ++ptr;
            }
            fprintf(stdout,"\nBitstream is %d channel, %ldHz\n",vi.channels,vi.rate);
            fprintf(stdout,"Encoded by: %s\n\n",vc.vendor);
        }
        convsize = BUF_SIZE/vi.channels;

        /* 开始解码出数据 */
        if(vorbis_synthesis_init(&vd, &vi) != 0 || vorbis_block_init(&vd, &vb))
        {
            fprintf(stderr, "vorbis_synthesis_init or vorbis_block_init failed.\n");
            exit(1);
        }

        int eos = 0;
        while(!eos){
            int result = 1;
            do{
                result = ogg_sync_pageout(&oy, &og);
                if(result < 0)
                {
                    fprintf(stderr,"Corrupt or missing data in bitstream; continuing...\n");
                    exit(1);
                }
                if(result == 1) // 1-成功 0-数据不够 <0-出错
                {
                    // ogg_stream_pagein 不管返回值,有问题的话 ogg_stream_packetout 会报错
                    ogg_stream_pagein(&os, &og);

                    while(1)
                    {
                        /* 循环处理 */
                        result = ogg_stream_packetout(&os, &op);
                        if(result == 1)
                        {
                            /* 数据处理 */
                            if(vorbis_synthesis(&vb,&op) == 0)
                                vorbis_synthesis_blockin(&vd, &vb);

                            /* 解出数据 */
                            float **pcm;
                            int samples;
                            while((samples = vorbis_synthesis_pcmout(&vd, &pcm)) > 0)
                            {
                                int bout = (samples < convsize ? samples : convsize);
                                printf("decode samples: %d\n", bout);
                                for(i = 0; i < vi.channels; i++)
                                {
                                    ogg_int16_t *ptr = convbuffer + i;
                                    float *mono = pcm[i];
                                    for(int j = 0; j < bout; j++)
                                    {
                                        int val = floor(mono[j] * 32767.f + .5f);
                                        val = val > 32767 ? 32767 : val;
                                        val = val < -32767 ? -32767 : val;
                                        *ptr = val;
                                        /* 一个通道一个通道处理 */
                                        ptr += vi.channels;
                                    }
                                }
                                /* 写入解码后的数据 */
                                fwrite(convbuffer, sizeof(short) * vi.channels, bout, fp_out_pcm);

                                /* 告诉libvorbis我们消耗了多少个采样点 */
                                vorbis_synthesis_read(&vd, bout);
                            }
                        }
                        else if(result < 0)
                        {
                            fprintf(stderr,"Corrupt header.  Exiting.\n");
                            exit(1);
                        }
                        else if(result == 0)
                        {
                            break; /* 数据结束 */
                        }

                    }
                    if(ogg_page_eos(&og))
                        eos = 1;
                }
                else if(result == 0)
                {
                    break; /* 需要继续填充数据 */
                }
            }while(0);

            /* 数据不够出1个page,继续填充数据 */
            if(result == 0)
            {
                buffer = ogg_sync_buffer(&oy, BUF_SIZE);
                bytes = fread(buffer, 1, BUF_SIZE, fp_in_ov);
                ogg_sync_wrote(&oy, bytes);
                total_read_bytes += bytes;
                printf("read = %d, total_read_bytes: %d\n", bytes, total_read_bytes);
            }
        }

        /* 结束了就清理状态 */
        vorbis_block_clear(&vb);
        vorbis_dsp_clear(&vd);

        /* 逻辑流结束了 */
        ogg_stream_clear(&os);
        vorbis_comment_clear(&vc);
        vorbis_info_clear(&vi);  /* 必须最后调用 */
    }
    ogg_sync_clear(&oy);

exit:
    if(fp_in_ov) fclose(fp_in_ov);
    if(fp_out_pcm) fclose(fp_out_pcm);

    return ret;
}

3、demo下载地址(任选一个)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-QWERT

你的鼓励是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值