ffmpeg+sdl音频播放器

主机环境:Windows XP

SDL版本:SDL2-2.0.3

ffmpeg版本:ffmpeg.2.4

ffmpeg库版本:ffmpeg-20140916-git-b76d613-win32-dev.7z、ffmpeg-20140916-git-b76d613-win32-shared.7z

开发环境:CodeBlocks13.12

参考ffmpeg指导教程并结合前面的SDL音频播放分析以及ffmpeg的重采样,编写音频播放器,代码如下

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>
#include <libavutil/mem.h>
#include <libswscale/swscale.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#include <SDL2/SDL.h>
#include <stdbool.h>

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define    rmask 0xff000000
#define    gmask 0x00ff0000
#define    bmask 0x0000ff00
#define    amask 0x000000ff
#else
#define    rmask  0x000000ff
#define    gmask  0x0000ff00
#define    bmask  0x00ff0000
#define    amask  0xff000000
#endif

#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
typedef struct PacketQueue
{
    AVPacketList *first_pkt, *last_pkt;//首尾指针
    int nb_packets;//包数
    int size;//包中的字节数
    SDL_mutex *mutex;//互斥变量
    SDL_cond *cond;//条件变量
} PacketQueue;
PacketQueue audioQ;

struct SwrContext *audio_swr_ctx;//重采样上下文
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//输出声道布局
int out_nb_samples = 1024;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输出源采样格式
int out_rate = 44100;//输出源的采样率
int out_nb_channels = 0;//得到通道数
int out_buffer_size = 0;//输出源的缓存大小

void packet_queue_init(PacketQueue *pq)
{
    memset(pq,0,sizeof(PacketQueue));//清空结构体
    pq->mutex = SDL_CreateMutex();//创建一个互斥量
    pq->cond = SDL_CreateCond();//创建一个条件量
}
/**
first_pkt指针指向第一个数据包元素,每次更新last_pkt指针,指向最后一个数据包元素
**/
int packet_queue_put(PacketQueue *pq, AVPacket *pkt)
{
    AVPacketList *pktlist;
    if(av_dup_packet(pkt) < 0)
    {
        printf("dup packet error!\n");
        //将数据拷贝至私有缓存中
        return -1;
    }
    pktlist = av_malloc(sizeof(AVPacketList));
    if(!pktlist)
    {
        return -1;
    }
    pktlist->pkt = *pkt;//初始化列表元素
    pktlist->next = NULL;//初始化列表元素

    SDL_LockMutex(pq->mutex);//上锁
    if(!pq->last_pkt)
    {
        //如果last_pkt指针为空,则重新链接first_pkt指针至新建的pktlist元素
        pq->first_pkt = pktlist;
    }
    else
    {
        //如果last指针不为空,则将pktlist元素添加到last链表末端
        pq->last_pkt->next = pktlist;
    }
    pq->last_pkt = pktlist;//将last_pkt指针指向新添加的pktlist元素,移动last_pkt指针
    pq->nb_packets++;//数据包个数增加
    pq->size+=pktlist->pkt.size;//数据大小增加
    SDL_CondSignal(pq->cond);//通知条件变量
    SDL_UnlockMutex(pq->mutex);//解锁
    return 0;
}
int quit = 0;
/**
从队列中取出一个数据包block:标记是否以阻塞方式取数据包
**/
static int packet_queue_get(PacketQueue *pq, AVPacket *ptk, int block)
{
    AVPacketList *pktlist;
    int ret;
    SDL_LockMutex(pq->mutex);//上锁
    static bool flag = false;
    while(1)
    {
        if(quit)
        {
            ret = -1;
            break;
        }
        pktlist = pq->first_pkt;//从第一个数据包开始提取
        if(pktlist)
        {
            //如果该数据包存在的话
            pq->first_pkt = pktlist->next;//移动队列的first指针到下一个数据包
            if(!pq->first_pkt)
            {
                //如果first指针指向的数据包为空,则清除last数据包指针(说明队列里已经没有数据包了)
                pq->last_pkt = NULL;
            }
            pq->nb_packets --;//数据包个数自减
            pq->size -= pktlist->pkt.size;//数据大小自减
            *ptk = pktlist->pkt;//更新数据包
            av_free(pktlist);//释放数据包列表
            ret = 1;
            flag = true;
            break;
        }
        else if(!block)
        {
            //如果是非阻塞方式直接返回
            ret = 0;
            if(flag)//用于指示音频队列里已经没有数据了
                ret = 2;
            break;
        }
        else
        {
            //如果是阻塞方式则进入睡眠模式等待唤醒
            SDL_CondWait(pq->cond,pq->mutex);//等待信号变量
        }
    }
    SDL_UnlockMutex(pq->mutex);//解锁
    return ret;
}
/*
解码一个音频数据包,返回解码的数据大小,一个音频包可能包含多个音频帧,但一次只解码一个音频帧,
所以一包音频可能需要多次才能解码完,使用while语句判断包数据是否全部解完,如果没有就解码当前包中的帧
,修改状态参数,否则,释放数据包,再从队列中取数据,记录初始值,再进循环。
*/
int audio_decode_frame(AVCodecContext *aCodecCtx,uint8_t *out_buffer,AVFrame *frame)
{
    static AVPacket pkt;//音频数据包,从队列中取出的数据包存放在这里
    static uint8_t *audio_pkd_data = NULL;//音频数据地址
    static int audio_pkt_size = 0;//音频包数据大小
    int len1;
    int got_frame;
    int ret,result,out_sample_size;//输出的解码样品大小
    static uint32_t i = 0;
    for(;;)
    {
        while(audio_pkt_size > 0)
        {
            //音频包数据还未解码完,继续执行解码操作
            out_sample_size = pkt.size;
            got_frame = 0;
            printf("pkt.size left:%d\n",out_sample_size);
            len1 = avcodec_decode_audio4(aCodecCtx,frame,&got_frame,&pkt);//解码音频,从packet到frame中
            if(len1 < 0)
            {
                //如果有错误发生
                audio_pkt_size = 0;
                break;
            }
            printf("len1:%d\n",len1);
            if(got_frame>0)
            {
                //得到解码帧后执行重采样输出至out_buffer中
                ret = swr_convert(audio_swr_ctx,&out_buffer,192000,(const uint8_t **)frame->data,frame->nb_samples);
                if(ret < 0)
                {
                    //转换失败
                    return -1;
                }
                out_sample_size = av_samples_get_buffer_size(NULL,out_nb_channels,ret,out_sample_fmt,1);
                printf("ret:%d\tout_sample_size:%d\n",ret,out_sample_size);//解码后的数据帧大小
                //对于sample.wav来说ret=frame->nb_samples
            }
            if(out_sample_size<=0)
                continue;
            pkt.data += len1;//移动未解码数据指针
            pkt.size -= len1;//更新未解码数据大小
            audio_pkd_data += len1;//更新已解码指针
            audio_pkt_size -= len1;//更新未解码数据大小
            return out_sample_size;//解码完成返回解码的数据
        }
        if(pkt.data)
            av_free_packet(&pkt);
        memset(&pkt,0,sizeof(pkt));
        if(quit)
            return -1;
        result = packet_queue_get(&audioQ,&pkt,0);
        if(result<0)//从队列中取出一个音频数据包 从这里展开!!!
            return -1;//读取失败
        else if(result == 2)
            return -2;
        audio_pkd_data = pkt.data;//初始化数据包地址及大小,用于一包数据有多个音频帧的情况
        audio_pkt_size = pkt.size;//得到音频数据包的大小
        printf("audio_pkt_size:%d\n",audio_pkt_size);
        printf("get the packet%d\n",i);
        i++;
    }
}
void audio_callback(void *userdata, Uint8 *stream, int len)
{
    AVCodecContext *aCodecCtx = (AVCodecContext*)userdata;
    int len1, audio_size=0;
    static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE*3)/2];
    static unsigned int audio_buf_size = 0;//解码后的音频数据大小
    static unsigned int audio_buf_index = 0;//已输出音频数据大小
    AVFrame *frame;
    frame = av_frame_alloc();
    if(frame == NULL)
    {
        printf("alloc frame failed!\n");
        av_frame_free(&frame);
        return;
    }

    while(len > 0)
    {
        printf("len:%d\n",len);
        if(audio_buf_index >= audio_buf_size)//音频已播放完需要更多的数据
        {
            audio_size = audio_decode_frame(aCodecCtx,audio_buf,frame);//获取解码的数据大小,数据在frame中一个帧
            if(audio_size < 0)
            {
                //解码失败,则播放静音缓存区写0即可
                if(audio_size== -2)
                {
                     SDL_PauseAudio(1);
                     return;
                }

                audio_buf_size = 1024;
                memset(stream,0,audio_buf_size);

            }
            else
            {
                audio_buf_size = audio_size;//获取解码后的音频帧大小
            }
            audio_buf_index = 0;//刷新已输出音频数据大小
        }
        len1 = audio_buf_size - audio_buf_index;//获取需要播放本次解码音频帧的缓存区大小
        if(len1 > len)
            len1 = len;//如果帧大小比SDL缓存区还要大,则分割播放
        SDL_memcpy(stream,(uint8_t *)audio_buf + audio_buf_index,len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;//更新已输出音频数据大小
    }
    av_frame_free(&frame);
}

int main(int argc, char* args[])
{
    av_register_all();
    AVFormatContext* pFormatCtx;

    if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0)
    {
        printf("SDL init failed error:%s\n",SDL_GetError());
        return -1;
    }
    pFormatCtx = avformat_alloc_context();
    if(pFormatCtx == NULL)
    {
        printf("couldn't alloc a avformatcontext\n");
        return -1;
    }
    if(avformat_open_input(&pFormatCtx,"sample.wav",NULL, NULL) != 0)
    {
        printf("couldn't open file!\n");
        return -1;
    }
    if(avformat_find_stream_info(pFormatCtx,NULL) != 0)
    {
        printf("couldn't find stream information!\n");
        return -1;
    }
    printf("nb_streams:%d\n",pFormatCtx->nb_streams);
    int i = 0,audioStream = -1;
    AVCodecContext *aCodecCtx;//视频、音频
    //一个文件一般有两种流:一个音频流、一个视频流
    for(i = 0; i < pFormatCtx->nb_streams; i++)
    {
        printf("nb_streams[%d]:%d\n",i,pFormatCtx->streams[i]->codec->codec_type);
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioStream < 0)
        {
            audioStream = i;
        }
    }

    if(audioStream == -1)
    {
        printf("couldn't find a audio stream!\n");
        return -1;
    }
    //here we find a video stream and a audio stream
    aCodecCtx = pFormatCtx->streams[audioStream]->codec;

    AVCodec *aCodec;//视频、音频编码器
    //查找音频解码器
    aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
    if(aCodec == NULL)
    {
        printf("unsupported audio codec!\n");
        return -1;
    }
    //打开音频解码器
    if(avcodec_open2(aCodecCtx,aCodec,NULL) < 0)
    {
        printf("open the audio codec failed!\n");
        return -1;
    }
    packet_queue_init(&audioQ);//初始化音频数据包队列
    //===================================前期工作完成======================================
    SDL_Surface *screen = NULL;
    SDL_Window* gWindow = NULL;

    gWindow = SDL_CreateWindow( "XPlayer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 480, 320, SDL_WINDOW_SHOWN );
    if(gWindow == NULL)
    {
        printf("create window failed:%s",SDL_GetError());
        return -1;
    }
    screen = SDL_GetWindowSurface(gWindow);
    if(screen == NULL)
    {
        printf("create surface failed:%s",SDL_GetError());
        return -1;
    }
    SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,0xff,0xff,0xff));//填充白色
    SDL_UpdateWindowSurface(gWindow);

    //把申请的内存和rgb24帧结合起来,准备读取数据
    int ret;
    AVPacket packet;//存放从文件中读取的数据包

    int64_t in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels);
    audio_swr_ctx = swr_alloc();
    if(!audio_swr_ctx)
    {
        printf("could not allocate resampler context!\n");
        exit(1);
    }
    out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);//得到输出源通道数

    printf("in_channel_layout:%s\n",av_get_channel_name(in_channel_layout));
    printf("out_channel_layout:%s\n",av_get_channel_name(out_ch_layout));
    printf("in_channel_number:%d\n",aCodecCtx->channels);
    printf("out_channel_number:%d\n",out_nb_channels);
    printf("in_sample_fmt:%s\n",av_get_sample_fmt_name(aCodecCtx->sample_fmt));
    printf("out_sample_fmt:%s\n",av_get_sample_fmt_name(out_sample_fmt));
    printf("in_sample_rate:%d\n",aCodecCtx->sample_rate);

    out_buffer_size = av_samples_get_buffer_size(NULL,out_nb_channels,out_nb_samples,out_sample_fmt,1);

    av_opt_set_int(audio_swr_ctx,"in_channel_layout",in_channel_layout,0);
    av_opt_set_int(audio_swr_ctx,"in_sample_rate",aCodecCtx->sample_rate,0);
    av_opt_set_sample_fmt(audio_swr_ctx,"in_sample_fmt",aCodecCtx->sample_fmt,0);

    av_opt_set_int(audio_swr_ctx,"out_channel_layout",out_ch_layout,0);
    av_opt_set_int(audio_swr_ctx,"out_sample_rate",out_rate,0);
    av_opt_set_sample_fmt(audio_swr_ctx,"out_sample_fmt",out_sample_fmt,0);

    ret = swr_init(audio_swr_ctx);//初始化重采样结构体
    if(ret < 0)
    {
        printf("failed to init the resampling context!\n");
        exit(1);
    }

    SDL_AudioSpec wanted_spec,obtained_spec;//前者是我们想要的配置,后者是我们实际用的配置
    wanted_spec.freq = out_rate;//输出源的采样率
    wanted_spec.format = AUDIO_S16SYS;//音频格式
    wanted_spec.channels = out_nb_channels;//输出源的通道数
    wanted_spec.silence = 0;
    wanted_spec.samples = out_nb_samples;//缓存区大小1024
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = aCodecCtx;
    if(SDL_OpenAudio(&wanted_spec,&obtained_spec) < 0)
    {
        printf("open audio failed. sdl error:%s",SDL_GetError());
        return -1;
    }
    SDL_PauseAudio(0);//开始播放
    bool sdl_quit = false;
    SDL_Event e;
    printf("====start playing audio====\n");

    while(av_read_frame(pFormatCtx, &packet)>=0) //读取一个包并保存到packet中
    {
        if(packet.stream_index == audioStream)
        {
            //音频包处理
            packet_queue_put(&audioQ,&packet);//入队
        }
        // Free the packet that was allocated by av_read_frame
        else
            av_free_packet(&packet);
        while(SDL_PollEvent(&e) != 0)
        {
            if(e.type == SDL_QUIT)
            {
                sdl_quit = true;//exit
            }
            else
            {
                break;
            }
        }
        if(sdl_quit)
        {
            break;
        }
        SDL_Delay(1);
    }

    if(!sdl_quit)
    {
        while((SDL_GetAudioStatus() == SDL_AUDIO_PLAYING))
        SDL_Delay(1000);
    }

    printf("======stop playing audio======\n");
    SDL_CloseAudio();
    swr_free(&audio_swr_ctx);

    SDL_DestroyWindow(gWindow);
    gWindow = NULL;
    SDL_Quit();


    // Close the codec

    avcodec_close(aCodecCtx);
    // Close the video file
    avformat_close_input(&pFormatCtx);
    return 0;
}

ffmpeg指导教程: http://dranger.com/ffmpeg/ffmpeg.html

工程代码下载:http://download.csdn.net/detail/key123zhangxing/7989861

你可能感兴趣的:(ffmpeg+sdl音频播放器)