简单音频播放:音频播放核心实现
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文件 截图 -- 纪念