1. 程式人生 > >最簡單的基於FFMPEG+SDL的音訊播放器 ver2 (採用SDL2 0)

最簡單的基於FFMPEG+SDL的音訊播放器 ver2 (採用SDL2 0)

               

=====================================================

最簡單的基於FFmpeg的音訊播放器系列文章列表:

=====================================================

簡介

之前做過一個簡單的音訊播放器:《最簡單的基於FFMPEG+SDL的音訊播放器》,採用的是SDL1.2。前兩天剛把原先做的《最簡單的基於FFMPEG+SDL的視訊播放器》更新採用了SDL2.0,於是順手也把音訊播放器更新成為SDL2.0.

需要注意的是,與播放視訊有很大的不同,SDL2.0播放音訊的函式相對於SDL1.2來說變化很小。基本上保持了不變。

除了使用SDL2.0之外,修改瞭如下地方:

*重建了工程,刪掉了不必要的程式碼,把程式碼修改得更規範更易懂。

*可以通過巨集控制是否使用SDL,以及是否輸出PCM。

*支援MP3,AAC等多種格式

原始碼

/** * 最簡單的基於FFmpeg的音訊播放器 2  * Simplest FFmpeg Audio Player 2  * * 雷霄驊 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程式實現了音訊的解碼和播放。 * 是最簡單的FFmpeg音訊解碼方面的教程。 * 通過學習本例子可以瞭解FFmpeg的解碼流程。 * * 該版本使用SDL 2.0替換了第一個版本中的SDL 1.0。 * 注意:SDL 2.0中音訊解碼的API並無變化。唯一變化的地方在於 * 其回撥函式的中的Audio Buffer並沒有完全初始化,需要手動初始化。 * 本例子中即SDL_memset(stream, 0, len); * * This software decode and play audio streams. * Suitable for beginner of FFmpeg. * * This version use SDL 2.0 instead of SDL 1.2 in version 1 * Note:The good news for audio is that, with one exception,  * it's entirely backwards compatible with 1.2. * That one really important exception: The audio callback  * does NOT start with a fully initialized buffer anymore.  * You must fully write to the buffer in all cases. In this  * example it is SDL_memset(stream, 0, len); * * Version 2.0 */
#include <stdio.h>#include <stdlib.h>#include <string.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32//Windowsextern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswresample/swresample.h"#include "SDL2/SDL.h"};#else//Linux...#ifdef __cplusplusextern "C"{#endif
#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswresample/swresample.h>#include <SDL2/SDL.h>#ifdef __cplusplus};#endif#endif#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio//Output PCM#define OUTPUT_PCM 1//Use SDL#define USE_SDL 1//Buffer://|-----------|-------------|//chunk-------pos---len-----|static  Uint8  *audio_chunk; static  Uint32  audio_len; static  Uint8  *audio_pos; /* The audio function callback takes the following parameters:  * stream: A pointer to the audio buffer to be filled  * len: The length (in bytes) of the audio buffer */ void  fill_audio(void *udata,Uint8 *stream,int len){  //SDL 2.0 SDL_memset(stream, 0, len); if(audio_len==0)  return;  len=(len>audio_len?audio_len:len); /*  Mix  as  much  data  as  possible  */  SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); audio_pos += len;  audio_len -= len; } //-----------------int main(int argc, char* argv[]){ AVFormatContext *pFormatCtx; int    i, audioStream; AVCodecContext *pCodecCtx; AVCodec   *pCodec; AVPacket  *packet; uint8_t   *out_buffer; AVFrame   *pFrame; SDL_AudioSpec wanted_spec;    int ret; uint32_t len = 0int got_picture; int index = 0int64_t in_channel_layout; struct SwrContext *au_convert_ctx; FILE *pFile=NULLchar url[]="xiaoqingge.mp3"; av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); //Open if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0){  printf("Couldn't open input stream.\n");  return -1; } // Retrieve stream information if(avformat_find_stream_info(pFormatCtx,NULL)<0){  printf("Couldn't find stream information.\n");  return -1; } // Dump valid information onto standard error av_dump_format(pFormatCtx, 0, url, false); // Find the first audio stream audioStream=-1for(i=0; i < pFormatCtx->nb_streams; i++)  if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){   audioStream=i;   break;  } if(audioStream==-1){  printf("Didn't find a audio stream.\n");  return -1; } // Get a pointer to the codec context for the audio stream pCodecCtx=pFormatCtx->streams[audioStream]->codec; // Find the decoder for the audio stream pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL){  printf("Codec not found.\n");  return -1; } // Open codec if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){  printf("Could not open codec.\n");  return -1; } #if OUTPUT_PCM pFile=fopen("output.pcm", "wb");#endif packet=(AVPacket *)av_malloc(sizeof(AVPacket)); av_init_packet(packet); //Out Audio Param uint64_t out_channel_layout=AV_CH_LAYOUT_STEREO; //nb_samples: AAC-1024 MP3-1152 int out_nb_samples=pCodecCtx->frame_size; AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; int out_sample_rate=44100int out_channels=av_get_channel_layout_nb_channels(out_channel_layout); //Out Buffer Size int out_buffer_size=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1); out_buffer=(uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); pFrame=av_frame_alloc();//SDL------------------#if USE_SDL //Init if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    printf( "Could not initialize SDL - %s\n", SDL_GetError());   return -1; } //SDL_AudioSpec wanted_spec.freq = out_sample_rate;  wanted_spec.format = AUDIO_S16SYS;  wanted_spec.channels = out_channels;  wanted_spec.silence = 0;  wanted_spec.samples = out_nb_samples;  wanted_spec.callback = fill_audio;  wanted_spec.userdata = pCodecCtx;  if (SDL_OpenAudio(&wanted_spec, NULL)<0){   printf("can't open audio.\n");   return -1;  } #endif //FIX:Some Codec's Context Information is missing in_channel_layout=av_get_default_channel_layout(pCodecCtx->channels); //Swr au_convert_ctx = swr_alloc(); au_convert_ctx=swr_alloc_set_opts(au_convert_ctx,out_channel_layout, out_sample_fmt, out_sample_rate,  in_channel_layout,pCodecCtx->sample_fmt , pCodecCtx->sample_rate,0, NULL); swr_init(au_convert_ctx); //Play SDL_PauseAudio(0); while(av_read_frame(pFormatCtx, packet)>=0){  if(packet->stream_index==audioStream){   ret = avcodec_decode_audio4( pCodecCtx, pFrame,&got_picture, packet);   if ( ret < 0 ) {                printf("Error in decoding audio frame.\n");                return -1;            }   if ( got_picture > 0 ){    swr_convert(au_convert_ctx,&out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);#if 1    printf("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);#endif#if OUTPUT_PCM    //Write PCM    fwrite(out_buffer, 1, out_buffer_size, pFile);#endif    index++;   }#if USE_SDL   while(audio_len>0)//Wait until finish    SDL_Delay(1);    //Set audio buffer (PCM data)   audio_chunk = (Uint8 *) out_buffer;    //Audio buffer length   audio_len =out_buffer_size;   audio_pos = audio_chunk;#endif  }  av_free_packet(packet); } swr_free(&au_convert_ctx);#if USE_SDL SDL_CloseAudio();//Close SDL SDL_Quit();#endif #if OUTPUT_PCM fclose(pFile);#endif av_free(out_buffer); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0;}

下載

Simplest FFmpeg audio player 2

*注:修正版中又修正了以下問題:

1.PCM輸出的fwrite()的size有錯誤

2.PCM輸出的fclose()外面添加了巨集定義

3.部分編碼器(例如WMA)的AVCodecContext中的channel_layout沒有進行初始化。會導致SwrContext初始化失敗。改為通過channels(一定會初始化)計算channel_layout而不是直接取channel_layout的值。

更新-2.1 (2015.2.13)=========================================

這次考慮到了跨平臺的要求,調整了原始碼。經過這次調整之後,原始碼可以在以下平臺編譯通過:
VC++:開啟sln檔案即可編譯,無需配置。
cl.exe:開啟compile_cl.bat即可命令列下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本里面的引數。編譯命令如下。
::VS2010 Environmentcall "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"::[email protected] INCLUDE=include;%INCLUDE%::[email protected] LIB=lib;%LIB%::compile and linkcl simplest_ffmpeg_audio_player.cpp /MD /link SDL.lib SDLmain.lib avcodec.lib ^avformat.lib avutil.lib avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib ^/SUBSYSTEM:WINDOWS /OPT:NOREF
MinGW:MinGW命令列下執行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
g++ simplest_ffmpeg_audio_player.cpp -g -o simplest_ffmpeg_audio_player.exe \-I /usr/local/include -L /usr/local/lib \-lmingw32 -lSDL2main -lSDL2 -lavformat -lavcodec -lavutil -lswresample
GCC:Linux或者MacOS命令列下執行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
gcc simplest_ffmpeg_audio_player.cpp -g -o simplest_ffmpeg_audio_player.out -I /usr/local/include -L /usr/local/lib \-lSDL2main -lSDL2 -lavformat -lavcodec -lavutil -lswresample 
PS:相關的編譯命令已經儲存到了工程資料夾中

SourceForge、Github等上面已經更新。

更新-2.2 (2015.7.17)=========================================

增加了下面工程:

simplest_ffmpeg_audio_decoder:音訊解碼器。使用了libavcodec和libavformat。

simplest_audio_play_sdl2:使用SDL2播放PCM取樣資料的例子。