简单音频播放:音频播放核心实现

简单音频播放:音频播放核心实现

tag:C,音频播放,libsndfile,PortAudio

/* Create by zyzx
* Created 2008-08-09
* Modified 2008-08-09
*/

一、准备
       1、依赖库PortAudio( http://www.portaudio.com/)
             Win32平台编译见《 Win32环境PortAudio库编译简介:音频播放》

       2、依赖库libsndfile ( http://www.mega-nerd.com/libsndfile/)
             简介:是一个开源跨平台C库,用于解码一系列的音频文件格式如WAV,AIFF,SF等等。初期支持Mp3格式的,后来由于种种原因取消了对Mp3格式的支持。
             编译环境: MinGW+MSYS《 MinGW+MSYS的C\C++编程环境安装与升级》
             1)从libsndfile上下载libsndfile-1.0.17.tar.gz(当前最高版本)
             2)将libsndfile-1.0.17.tar.gz复制到MinGW/Msys安装目录Msys/home目录下
             3)打开Msys模拟环境
                   $ cd /home
                   $ tar jxf libsndfile-1.0.17.tar.gz && cd libsndfile-1.0.17
                   $ ./configure
                   $ make && make install
             4)如果懒点,直接使用源码包中发布的libsndfile-1.dll和sndfile.h文件得了。

二、编码试验
        嘿,,经过层层碰壁后,,小弟终于初窥了点门道,,成功的摸索出了一条小道。。不过不要紧,万事开头难,,接下来吗。。距离自己的音频播放器又近了一步。。

        值得注意的地方:
        如下源码参照 PortAudio库中示例patest_read_write_wire.c 和 libsndfile 库中示例sndfile-play.c 文件进行的修改。。所以还有很多地方懒得去理会,代码难看点。。
        1)其中 while(1) { ......} 语句,可以提取到一个线程中去。此线程专门负责给声卡喂数据,使用双缓存或三缓存,当某个缓存块数据全部喂到声卡后,通知数据读取线程开工了。
        2)为了节省空间咱不能一下申请WIN32_AUDIO_BUFF_LEN这大的内存区域,使用多线程来解决。添加数据读取线程。。
        3)为了可以播放,暂停,... 咱得提供一个GUI界面,所以就涉及到线程通讯了哦。。
        4)为了保证跨平台性,还得去找个跨平台的线程库啊。。Boost库中包含线程相关的,就先学习ing...


下面是源码:(还要在vs里设置库环境)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "portaudio.h"

#include "windows.h"
#include "sndfile.h"

#define SF_DLL        "libsndfile-1.dll"

typedef int                  (*P_sf_command)(SNDFILE *sndfile, int command, void *data, int datasize);
typedef SNDFILE*    (*P_sf_open)(const char *path, int mode, SF_INFO *sfinfo);
typedef sf_count_t     (*P_sf_read_short)(SNDFILE *sndfile, short *ptr, sf_count_t items);
typedef int                   (*P_sf_close)(SNDFILE *sndfile);
typedef int                  (*P_sf_perror)(SNDFILE *sndfile);
typedef const char*   (*P_sf_strerror)(SNDFILE *sndfile);
typedef sf_count_t    (*P_sf_read_float)(SNDFILE *sndfile, float *ptr, sf_count_t items);
typedef sf_count_t    (*P_sf_read_int)(SNDFILE *sndfile, int *ptr, sf_count_t items);

#define sf_command( hInstan, p )        P_sf_command p = (P_sf_command)GetProcAddress( hInstan, "sf_command")
#define sf_open( hInstan, p )            P_sf_open p = (P_sf_open)GetProcAddress( hInstan, "sf_open" )
#define sf_close( hInstan, p )            P_sf_close p = (P_sf_close)GetProcAddress( hInstan, "sf_close" )
#define sf_perror( hInstan, p )            P_sf_perror p = (P_sf_perror)GetProcAddress( hInstan, "sf_perror" )
#define sf_read_short( hInstan, p )        P_sf_read_short p = (P_sf_read_short)GetProcAddress( hInstan, "sf_read_short" )
#define sf_read_int( hInstan, p )        P_sf_read_int p = (P_sf_read_int)GetProcAddress( hInstan, "sf_read_int" )
#define sf_read_float( hInstan, p )        P_sf_read_float p = (P_sf_read_float)GetProcAddress( hInstan, "sf_read_float" )
#define sf_strerror( hInstan, p )        P_sf_strerror p = (P_sf_strerror)GetProcAddress( hInstan, "sf_strerror" )

//* - 整上面这些也是没办法啊,在VS里只有*.dll和头文件,咱只有动态加载了。

//* - 保存动态库句柄
HINSTANCE   ghIns;  

/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */
#define SAMPLE_RATE (44100)                                       //* - 采样率
#define FRAMES_PER_BUFFER ( 64 * 1024 )                 //* - 缓存大小 - 向声卡驱动写数据时候的
#define NUM_CHANNELS    (2)                                           //* - 声道数
/* #define DITHER_FLAG     (paDitherOff) */
#define DITHER_FLAG     (0) /**/

/* Select sample format. */

#define PA_SAMPLE_TYPE paFloat32                             //* - 采样点占用大小32位的float型
#define SAMPLE_SIZE (4)                                                   //* - 采样点占用4字节
#define SAMPLE_SILENCE (0.0f)
#define CLEAR(a) bzero( (a), FRAMES_PER_BUFFER * NUM_CHANNELS * SAMPLE_SIZE )
#define PRINTF_S_FORMAT "%.8f"

#define WIN32_AUDIO_BUFF_LEN    32 * 1024 * 1024 //* - 音频数据(解码后)缓冲区域大小

typedef struct
{
    float*        buffer;                      //* - 音频数据(解码后)缓冲区域

    long        current, bufferlen ;    //* - 当前播放到达的数据点,实际全部音频数据长度

    SNDFILE     *sndfile ;             //* - libsndfile 库 基本 结构 -- 类似句柄或结构体指针
    SF_INFO     sfinfo ;                //* - libsndfile 库 读取的音频基本信息(如采样率,声道,等等)

    sf_count_t    remaining ;         //* - 好像没用到
} Win32_Audio_Data ;


/*******************************************************************/
int main(int argc, char *argv [] );
int main(int argc, char *argv [] )
{
    HINSTANCE   hIns;
    hIns = LoadLibrary( SF_DLL );          //* 加载 libsndfile-1.dll

    Win32_Audio_Data audio_data;

    audio_data.buffer = new float[ WIN32_AUDIO_BUFF_LEN ] ;
    audio_data.current = 0;

    ghIns = hIns;

    PaStreamParameters inputParameters, outputParameters;
    PaStream *stream = NULL;
    PaError err;
    int i;
    int numBytes;


    printf("patest_read_write_wire.c\n"); fflush(stdout);

    err = Pa_Initialize();
    if( err != paNoError ) goto error;

    //* - 打开****.***音频文件
    sf_open( ghIns, Open );
    if (! (audio_data.sndfile = Open (argv[1], SFM_READ, &(audio_data.sfinfo))))
    {   
        sf_strerror( ghIns, Strerror );
        puts ( Strerror(NULL) ) ;

        goto error;
    }

    //* 以float格式读取并解析音频格式 长度标为WIN32_AUDIO_BUFF_LEN,基本上能全部读取文件
    sf_read_float( ghIns, ReadFloat );
    audio_data.bufferlen += (long) ReadFloat (audio_data.sndfile, (float*)(audio_data.buffer ), WIN32_AUDIO_BUFF_LEN/*audio_data.sfinfo.frames */) ;


    printf( "-------------------------------\n" );

    printf( "Output samplerate %d\n", audio_data.sfinfo.samplerate );
    printf( "Output frames %d\n", audio_data.sfinfo.frames );
    printf( "Output channels %d\n", audio_data.sfinfo.channels );
    printf( "seekable %d\n ", audio_data.sfinfo.seekable );
    printf( "format is %d\n ", audio_data.sfinfo.format );
    printf( "Sections is %d\n ", audio_data.sfinfo.sections );

    printf( "Read buffer len %d\n" , audio_data.bufferlen );

    printf( "-------------------------------\n" );

    //inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
    //printf( "Input device # %d.\n", inputParameters.device );
    //printf( "Input LL: %g s\n", Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency );
    //printf( "Input HL: %g s\n", Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency );
    //inputParameters.channelCount = audio_data.sfinfo.channels;//NUM_CHANNELS;
    //inputParameters.sampleFormat = PA_SAMPLE_TYPE;
    //inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
    //inputParameters.hostApiSpecificStreamInfo = NULL;

    outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
    printf( "Output device # %d.\n", outputParameters.device );
    printf( "Output LL: %g s\n", Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency );
    printf( "Output HL: %g s\n", Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency );
    outputParameters.channelCount = audio_data.sfinfo.channels;//NUM_CHANNELS;
    outputParameters.sampleFormat = PA_SAMPLE_TYPE;
    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    /* -- setup -- */

    err = Pa_OpenStream(
        &stream,
        NULL,//&inputParameters,                                         //* - 麦克风
        &outputParameters,                                                   //* - 扬声器
        audio_data.sfinfo.samplerate,//SAMPLE_RATE,   //* - 采样率
        FRAMES_PER_BUFFER,                                        //* - 缓冲区
        paClipOff,      /* we won't output out of range samples so don't bother clipping them */
        NULL, /* no callback, use blocking API */
        NULL ); /* no callback, so no callback userData */
    if( err != paNoError ) goto error;

    //err = Pa_StartStream( stream );
    //if( err != paNoError ) goto error;
    //printf("Wire on. Will run one minute.\n"); fflush(stdout);

    //for( i=0; i<(60*SAMPLE_RATE)/FRAMES_PER_BUFFER; ++i )
    //{
    //    err = Pa_WriteStream( stream, sampleBlock, FRAMES_PER_BUFFER );
    //    if( err ) goto xrun;
    //    err = Pa_ReadStream( stream, sampleBlock, FRAMES_PER_BUFFER );
    //    if( err ) goto xrun;
    //}
    //err = Pa_StopStream( stream );
    //if( err != paNoError ) goto error;

    //CLEAR( sampleBlock );

    err = Pa_StartStream( stream );
    if( err != paNoError ) goto error;
    printf("Wire on. Interrupt to stop.\n"); fflush(stdout);

    while( 1 )
    {
        //* - 此函数为同步函数,它一直等待到buffer中的数据全部写入声卡驱动才返回
        //* - 缓冲去长度为FRAMES_PER_BUFFER,指的是一个声道上的数据大小
        err = Pa_WriteStream( stream, audio_data.buffer + audio_data.current/*sampleBlock*/, FRAMES_PER_BUFFER );
        audio_data.current = audio_data.current + FRAMES_PER_BUFFER * NUM_CHANNELS ;

        //if( audio_data.current + FRAMES_PER_BUFFER * NUM_CHANNELS * SAMPLE_SIZE > audio_data.bufferlen ) break;

        printf( "%d\n", audio_data.current);

        if( err ) goto xrun;
        //err = Pa_ReadStream( stream, sampleBlock, FRAMES_PER_BUFFER );
        //if( err ) goto xrun;
    }
    err = Pa_StopStream( stream );
    if( err != paNoError ) goto error;

    Pa_CloseStream( stream );

    free( sampleBlock );

    Pa_Terminate();
    return 0;

xrun:
    if( stream ) {
        Pa_AbortStream( stream );
        Pa_CloseStream( stream );
    }
    free( sampleBlock );
    Pa_Terminate();
    if( err & paInputOverflow )
        fprintf( stderr, "Input Overflow.\n" );
    if( err & paOutputUnderflow )
        fprintf( stderr, "Output Underflow.\n" );

    delete [] audio_data.buffer;
    FreeLibrary( ghIns );
    return -2;

error:
    if( stream ) {
        Pa_AbortStream( stream );
        Pa_CloseStream( stream );
        FreeLibrary( ghIns );
    }
    free( sampleBlock );
    Pa_Terminate();
    fprintf( stderr, "An error occured while using the portaudio stream\n" );
    fprintf( stderr, "Error number: %d\n", err );
    fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );

    delete [] audio_data.buffer;
    FreeLibrary( ghIns );
    return -1;
}

三、某次播放Wav文件 截图 -- 纪念

TestPortAudio.png

你可能感兴趣的:(简单音频播放:音频播放核心实现)