6.基于FFMPEG+SDL2播放音频

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011003120/article/details/81950045

参考资料:
1.雷博博客
2. An ffmpeg and SDL Tutorial

前面了解了FFMPEG解码MP3文件为PCM,以及将PCM通过SDL2进行播放,下面就是将这两者进行结合,使之能够边解码边播放。。。。

一、综述

总共有2份代码,第一份是参考雷博的代码,第二份是在雷博的代码基础上进行修改,增加了链表队列控制。

二、代码1(基础代码)

关于FFMPEG解码PCM以及SDL播放音频相关知识在前两篇文章中已经详细描述了,所以不再在这里赘述,参考雷博的代码,将前面两篇文章中的代码进行综合,即可正常解码并播放音频。

大致流程为:
初始化复用器和解复用器—>获取输入文件的一些信息—->查找解码器并打开—>初始化SDL—>播放音频—->读出音频数据并解码—>等待SDL读取音频—>SDL回调读取音频数据—>结束。

其中,比较重要的便是回调函数,当设备需要音频数据时候便会调用此回调函数获取音频数据,因此在这个函数中,我们需要将解码后的音频数据赋值给stream。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif

//#define debug_msg(fmt, args...) printf("--->[%s,%d]  " fmt "\n\n", __FUNCTION__, __LINE__, ##args)

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  
                                     //48000 * (32/8)

unsigned int audioLen = 0;
unsigned char *audioChunk = NULL;
unsigned char *audioPos = NULL;

void fill_audio(void * udata, Uint8 * stream, int len)
{
    SDL_memset(stream, 0, len);

    if (audioLen == 0)
        return;

    len = (len>audioLen?audioLen:len);

    SDL_MixAudio(stream,audioPos,len,SDL_MIX_MAXVOLUME);

    audioPos += len;
    audioLen -= len;
}

int test_audio_2_play()
{
    AVFormatContext *pFortCtx = NULL;
    AVCodecContext *pCodecCtx = NULL;
    AVCodec *pCodec = NULL;
    AVPacket *pPkt = NULL;
    AVFrame*pFrame = NULL;
    struct SwrContext *pSwrCtx = NULL;
    SDL_AudioSpec wantSpec;

    //FILE* outFile = fopen("output.pcm", "wb");
    char inFile[] = "skycity1.mp3";

    int ret = -1;
    int audioIndex = -1;
    int i = 0;
    int got_picture = -1;

    uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道
    enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; //声音格式
    int out_sample_rate=44100;   //采样率
    int out_nb_samples = -1;   
    int out_channels = -1;        //通道数
    int out_buffer_size = -1;   //输出buff
    unsigned char *outBuff = NULL;

    uint64_t in_chn_layout = -1;  //通道布局 

    struct SwrContext *au_convert_ctx;

    av_register_all();

    pFortCtx = avformat_alloc_context();  

    if (avformat_open_input(&pFortCtx, inFile, NULL, NULL) != 0)   //open input file and read data into buf
    {
        printf("avformat_open_input error!\n");
        ret = -1;
        goto ERR_1;
    }

    if (avformat_find_stream_info(pFortCtx, NULL) < 0)   //find stream some info
    {
        printf("avformat_find_stream_info error!\n");
        ret = -1;
        goto ERR_1;
    }

    /* find audio index */
    for (i = 0; i < pFortCtx->nb_streams; i++)
    {
        if (pFortCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audioIndex = i;
            break;
        }
    }

    if (-1 == audioIndex)
    {
        printf("can not find audio index!\n");
        ret = -1;
        goto ERR_1;
    }

    printf("------>audioIndex is %d\n", audioIndex);

    pCodecCtx = pFortCtx->streams[audioIndex]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (NULL == pCodec)
    {
        printf("can not find decoder!\n");
        ret = -1;
        goto ERR_1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        ret = -1;
        goto ERR_1;
    }

    if (NULL == (pPkt = (AVPacket *)av_malloc(sizeof(AVPacket))))
    {
        printf("AV malloc failure.\n");
        ret = -1;
        goto ERR_2;
    }

    //out parameter
    out_nb_samples = pCodecCtx->frame_size;
    out_channels = av_get_channel_layout_nb_channels(out_chn_layout);
    out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples,  out_sample_fmt, 1);
    outBuff = (unsigned char *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //双声道
    printf("-------->out_buffer_size is %d\n",out_buffer_size);
    in_chn_layout = av_get_default_channel_layout(pCodecCtx->channels);

    pFrame = av_frame_alloc();
    //SDL
    wantSpec.freq = out_sample_rate;
    wantSpec.format = AUDIO_S16SYS;
    wantSpec.channels = out_channels;
    wantSpec.silence = 0;
    wantSpec.samples = out_nb_samples;
    wantSpec.callback = fill_audio;
    wantSpec.userdata = pCodecCtx;
    if (SDL_OpenAudio(&wantSpec, NULL) < 0)
    {
        printf("can not open SDL!\n");
        ret = -1;
        goto ERR_3;
    }


    //Swr
    au_convert_ctx=swr_alloc_set_opts(NULL,
    out_chn_layout,                                /*out*/
    out_sample_fmt,                              /*out*/
    out_sample_rate,                             /*out*/
    in_chn_layout,                                  /*in*/
    pCodecCtx->sample_fmt ,               /*in*/
    pCodecCtx->sample_rate,               /*in*/
    0, 
    NULL);

    swr_init(au_convert_ctx);

    SDL_PauseAudio(0);

    while(av_read_frame(pFortCtx, pPkt) >= 0)
    {
        if (pPkt->stream_index == audioIndex)
        {
            if (avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, pPkt) < 0)
            {
                printf("Error in decoding audio frame.\n");
                ret = -1;
                break;
            }

            if (got_picture > 0)
            {
                swr_convert(au_convert_ctx,&outBuff, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);
                //fwrite(outBuff, 1, out_buffer_size, outFile); 
                while(audioLen > 0)
                    SDL_Delay(1); 

                audioChunk = (unsigned char *)outBuff;
                audioPos = audioChunk;
                audioLen = out_buffer_size;

            }
        }
        av_free_packet(pPkt);
    }

    SDL_CloseAudio();
    SDL_Quit();

    swr_free(&au_convert_ctx);
    ERR_3:
    av_free(outBuff);
    ERR_2:
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFortCtx);
    ERR_1:
    avformat_free_context(pFortCtx);
    //fclose(outFile);
    return ret;
}

int main(int argc, char *argv[])
{
    //test_audio_2_PCM();
    test_audio_2_play();

    return 0;
}

三、代码2(增加链表队列)

在这份代码中,主要是增加了链表队列,并将解码工作放在了回调函数中。
和上面的区别是:
主程序中,每读取一包的数据(av_read_frame)将其放入链表中
在回调函数中,从链表中取出每包的数据并将其解码放入stream中

1)链表结构

链表结构如下:

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

其中,first_pkt和last_pkt为两个主要的链表结点,first_pkt用于指向第一个链表结点,当我们取数据的时候,总是取出第一个结点的数据,并将第一个结点的next重新复制给first_pkt,用于下次取数据时使用,last_pkt为最后一个结点,当放数据时候,会将新的数据结点地址赋值给last_pkt的next,并将last_pkt重新指向最后一个结点,依此反复,不断将新的数据结点添加到链表的最后。
其原型为:

typedef struct AVPacketList {
    AVPacket pkt;
    struct AVPacketList *next;
} AVPacketList;

nb_packets为当前的总packet数目
size 为当前所有packet中数据的大小
mutex 为互斥锁
cond 为条件变量

因为主线程会不断向链表中放入读取到的数据以及回调函数中会不断从链表中读取数据,所以需要有互斥锁以及条件变量来进行同步操作。

2)放数据到队列中

主函数中将读取到的数据放到队列中,函数原型如下:

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
    AVPacketList *pkt1;

    if(av_dup_packet(pkt) < 0) {
        return -1;
    }

    pkt1 = (AVPacketList *)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
        return -1;

    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);

    if (!q->last_pkt)    //队列为空
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;  //将当前链表的最后一个结点的next指向pkt1

    q->last_pkt = pkt1; //将last_pkt指向最后一个结点,即pkt1
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);
  return 0;
}

注意,在进行链表操作的时候,需要加锁解锁,防止其他地方同时进行操作。

3)从队列中取数据

从链表队列中取出结点数据,如下:

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for(;;) 
    {
        if(quit) 
        {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt; //指向第一个结点,即取出第一个结点
        if (pkt1) 
        {
            q->first_pkt = pkt1->next; //即q->first_pkt = q->first_pkt->next 将第一个结点指向的下一个结点设置为first_pkt,可以理解为取出当前第一个结点

            if (!q->first_pkt)
                q->last_pkt = NULL;

            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;
            av_free(pkt1);
            ret = 1;
            break;
        }
        else if (!block) 
        {
            ret = 0;
            break;
        } 
        else 
        {
            SDL_CondWait(q->cond, q->mutex);
        }
    }

    SDL_UnlockMutex(q->mutex);
    return ret;
}

4)回调函数

void audio_callback(void *userdata, Uint8 *stream, int len) {
    AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
    int len1, audio_size;

    static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    while(len > 0) 
    {   
        if(audio_buf_index >= audio_buf_size)  //表示当前audio_buf中已经没有数据,需要解码数据了
        {
            /* We have already sent all our data; get more */
            audio_size = audio_decode_frame(aCodecCtx, audio_buf,sizeof(audio_buf));
            if(audio_size < 0) 
            {
                /* If error, output silence */
                audio_buf_size = 1024;
                memset(audio_buf, 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;

        memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
} 

在上面代码中有三个static 变量:
audio_buf 、audio_buf_size、 audio_buf_index
audio_buf中存放的为解码后的数据
audio_buf_size 为当前audio_buf中数据的长度
audio_buf_index为当前指向audio_buf的index

之所以设置为static,是因为当调用此回调函数时候,SDL期望的数据长度为len,但是可能我们调用audio_decode_frame返回的数据总长度大于len,所以将长度为len的数据赋值给stream,当下一次回调进来的时候,先根据audio_buf_size和audio_buf_index的大小来进行判断是否需要再解码新的数据,如果还有剩余数据,则继续将audio_buf中的剩余数据赋值给stream,此时如果剩余数据长度小于len,则循环解码新的数据进行赋值。

5)解码数据

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    static AVFrame frame;

    int len1, data_size = 0;

    for(;;) 
    {
        while(audio_pkt_size > 0) 
        {
            int got_frame = 0;
            len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
            if(len1 < 0) 
            {
                /* if error, skip frame */
                audio_pkt_size = 0;
                break;
                }

            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            data_size = 0;

            if(got_frame) 
            {
                swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);

                data_size = out_buffer_size;

                /*data_size = av_samples_get_buffer_size(NULL, 
                            aCodecCtx->channels,
                            frame.nb_samples,
                            aCodecCtx->sample_fmt,
                            1);
                assert(data_size <= buf_size);
                memcpy(audio_buf, frame.data[0], data_size);
                */
            }

            if(data_size <= 0) 
            {
                /* No data yet, get more frames */
                continue;
            }

            /* We have data, return it and come back for more later */
            return data_size;
        }

        if(pkt.data)
            av_free_packet(&pkt);

        if(quit) {
            return -1;
        }

        if(packet_queue_get(&audioq, &pkt, 1) < 0) 
        {
            return -1;
        }

        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}

在这个函数中,仍然有几个static变量,pkt/audio_pkt_data/audio_pkt_size/frame.

首先我们先了解一下解码函数avcodec_decode_audio4中的一些描述:

  • Some decoders may support multiple frames in a single AVPacket. Such
  • decoders would then just decode the first frame and the return value would be
  • less than the packet size. In this case, avcodec_decode_audio4 has to be
  • called again with an AVPacket containing the remaining data in order to
  • decode the second frame, etc… Even if no frames are returned, the packet
  • needs to be fed to the decoder with remaining data until it is completely
  • consumed or an error occurs.

上面大意是,一个AVPacket中可能会有多帧音频数据,但是avcodec_decode_audio4每次只解码一帧的数据,因此需要我们多次调用此接口来解码AVPacket中的数据。

根据以上,所以当audio_decode_frame解码一帧后就返回,当下次进来此函数时候,会继续将pkt里面的剩余数据进行解码,当将pkt里面的剩余数据解码万之后,在下次进来的时候会重新从队列里面读取数据,audio_pkt_size和audio_pkt_data则是与此过程相关。

同时,解码后的数据格式可能不是我们想要的或者SDL2无法播放,则需要调用SwrContext进行重采样,如下:

swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);

以上便是几个重要的函数解析,下面是完整的代码:

//采用队列的方式
//linux下编译命令如下:
//gcc test_2_pcm.c -o test -I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil -lswresample

//windows下可以直接套用雷博的工程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif

//#define debug_msg(fmt, args...) printf("--->[%s,%d]  " fmt "\n\n", __FUNCTION__, __LINE__, ##args)

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  
                                     //48000 * (32/8)
#define SDL_AUDIO_BUFFER_SIZE 1024

struct SwrContext *au_convert_ctx;
int out_buffer_size = -1;   //输出buff长度

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

PacketQueue audioq;

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->first_pkt = NULL;
  q->last_pkt = NULL;
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
    AVPacketList *pkt1;

    if(av_dup_packet(pkt) < 0) {
        return -1;
    }

    pkt1 = (AVPacketList *)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
        return -1;

    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);

    if (!q->last_pkt)    //队列为空
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;  //将当前链表的最后一个结点的next指向pkt1

    q->last_pkt = pkt1; //将last_pkt指向最后一个结点,即pkt1
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);
  return 0;
}

int quit = 0;

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for(;;) 
    {
        if(quit) 
        {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt; //指向第一个结点,即取出第一个结点
        if (pkt1) 
        {
            q->first_pkt = pkt1->next; //即q->first_pkt = q->first_pkt->next 将第一个结点指向的下一个结点设置为first_pkt,可以理解为取出当前第一个结点

            if (!q->first_pkt)
                q->last_pkt = NULL;

            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;
            av_free(pkt1);
            ret = 1;
            break;
        }
        else if (!block) 
        {
            ret = 0;
            break;
        } 
        else 
        {
            SDL_CondWait(q->cond, q->mutex);
        }
    }

    SDL_UnlockMutex(q->mutex);
    return ret;
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    static AVFrame frame;

    int len1, data_size = 0;

    for(;;) 
    {
        while(audio_pkt_size > 0) 
        {
            int got_frame = 0;
            len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
            if(len1 < 0) 
            {
                /* if error, skip frame */
                audio_pkt_size = 0;
                break;
                }

            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            data_size = 0;

            if(got_frame) 
            {
                swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);

                data_size = out_buffer_size;

                /*data_size = av_samples_get_buffer_size(NULL, 
                            aCodecCtx->channels,
                            frame.nb_samples,
                            aCodecCtx->sample_fmt,
                            1);
                assert(data_size <= buf_size);
                memcpy(audio_buf, frame.data[0], data_size);
                */
            }

            if(data_size <= 0) 
            {
                /* No data yet, get more frames */
                continue;
            }

            /* We have data, return it and come back for more later */
            return data_size;
        }

        if(pkt.data)
            av_free_packet(&pkt);

        if(quit) {
            return -1;
        }

        if(packet_queue_get(&audioq, &pkt, 1) < 0) 
        {
            return -1;
        }

        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {

    AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
    int len1, audio_size;

    static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    while(len > 0) 
    {   
        if(audio_buf_index >= audio_buf_size) //表示当前audio_buf中已经没有数据,需要解码数据了
        {
            /* We have already sent all our data; get more */
            audio_size = audio_decode_frame(aCodecCtx, audio_buf,sizeof(audio_buf));
            if(audio_size < 0) 
            {
                /* If error, output silence */
                audio_buf_size = 1024;
                memset(audio_buf, 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;

        memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
} 
int test_audio_2_play()
{
    AVFormatContext *pFortCtx = NULL;
    AVCodecContext *pCodecCtx = NULL;
    AVCodec *pCodec = NULL;
    AVPacket *pPkt = NULL;
    AVFrame*pFrame = NULL;
    struct SwrContext *pSwrCtx = NULL;

    SDL_AudioSpec wantSpec;

    //FILE* outFile = fopen("output.pcm", "wb");
    char inFile[] = "skycity1.mp3";

    int ret = -1;
    int audioIndex = -1;
    int i = 0;
    int got_picture = -1;

    uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道
    enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; //声音格式
    int out_sample_rate=44100;   //采样率
    int out_nb_samples = -1;   
    int out_channels = -1;        //通道数
    unsigned char *outBuff = NULL;

    uint64_t in_chn_layout = -1;  //通道布局 


    av_register_all();

    pFortCtx = avformat_alloc_context();  

    if (avformat_open_input(&pFortCtx, inFile, NULL, NULL) != 0)   //open input file and read data into buf
    {
        printf("avformat_open_input error!\n");
        ret = -1;
        goto ERR_1;
    }

    if (avformat_find_stream_info(pFortCtx, NULL) < 0)   //find stream some info
    {
        printf("avformat_find_stream_info error!\n");
        ret = -1;
        goto ERR_1;
    }

    /* find audio index */
    for (i = 0; i < pFortCtx->nb_streams; i++)
    {
        if (pFortCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audioIndex = i;
            break;
        }
    }

    if (-1 == audioIndex)
    {
        printf("can not find audio index!\n");
        ret = -1;
        goto ERR_1;
    }

    printf("------>audioIndex is %d\n", audioIndex);

    pCodecCtx = pFortCtx->streams[audioIndex]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (NULL == pCodec)
    {
        printf("can not find decoder!\n");
        ret = -1;
        goto ERR_1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        ret = -1;
        goto ERR_1;
    }

    if (NULL == (pPkt = (AVPacket *)av_malloc(sizeof(AVPacket))))
    {
        printf("AV malloc failure.\n");
        ret = -1;
        goto ERR_2;
    }

    //out parameter
    out_nb_samples = pCodecCtx->frame_size;
    out_channels = av_get_channel_layout_nb_channels(out_chn_layout);
    out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples,  out_sample_fmt, 1);
    outBuff = (unsigned char *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //双声道
    printf("-------->out_buffer_size is %d\n",out_buffer_size);
    in_chn_layout = av_get_default_channel_layout(pCodecCtx->channels);

    pFrame = av_frame_alloc();
    //SDL
    wantSpec.freq = out_sample_rate;
    wantSpec.format = AUDIO_S16SYS;
    wantSpec.channels = out_channels;
    wantSpec.silence = 0;
    wantSpec.samples = out_nb_samples;
    wantSpec.callback = audio_callback;
    wantSpec.userdata = pCodecCtx;
    if (SDL_OpenAudio(&wantSpec, NULL) < 0)
    {
        printf("can not open SDL!\n");
        ret = -1;
        goto ERR_3;
    }


    //Swr
    au_convert_ctx=swr_alloc_set_opts(NULL,
    out_chn_layout,                                /*out*/
    out_sample_fmt,                              /*out*/
    out_sample_rate,                             /*out*/
    in_chn_layout,                                  /*in*/
    pCodecCtx->sample_fmt ,               /*in*/
    pCodecCtx->sample_rate,               /*in*/
    0, 
    NULL);

    swr_init(au_convert_ctx);

    SDL_PauseAudio(0);

    while(av_read_frame(pFortCtx, pPkt) >= 0)
    {
        if (pPkt->stream_index == audioIndex)
        {
            #if 0
            if (avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, pPkt) < 0)
            {
                printf("Error in decoding audio frame.\n");
                ret = -1;
                break;
            }

            if (got_picture > 0)
            {
                swr_convert(au_convert_ctx,&outBuff, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);
                //fwrite(outBuff, 1, out_buffer_size, outFile); 
                while(audioLen > 0)
                    SDL_Delay(1); 

                audioChunk = (unsigned char *)outBuff;
                audioPos = audioChunk;
                audioLen = out_buffer_size;

            }
            #else
            packet_queue_put(&audioq, pPkt);
            #endif
        }
        else
            av_free_packet(pPkt);
    }

    sleep(10);

    SDL_CloseAudio();
    SDL_Quit();

    swr_free(&au_convert_ctx);
    ERR_3:
    av_free(outBuff);
    ERR_2:
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFortCtx);
    ERR_1:
    avformat_free_context(pFortCtx);
    //fclose(outFile);
    return ret;
}

int main(int argc, char *argv[])
{
    packet_queue_init(&audioq);

    //test_audio_2_PCM();
    test_audio_2_play();

    return 0;
}

最后是一个完整的工程,工程“无耻” ( ̄▽ ̄)~*的使用了雷博的工程,在visual studio 2010上跑通了,直接将代码写入工程编译运行。
基于FFMPEG+SDL2播放音频

猜你喜欢

转载自blog.csdn.net/u011003120/article/details/81950045