ffmpeg+SDL播放音頻(無雜音)

做者:Huatiangit

github:https://github.com/Huatiangithub

郵箱: 773512457@qq.com緩存

平臺:Fedora 24 (64bit)bash

前言

音頻常見概念:函數

  • 採樣率 sample rate    表示以多快的速度來播放這段樣本流,它的表示方式爲每秒多少次採樣
  • 採樣精度                         每一個採樣 sample所用的二進制位數
  • 聲道數                              通常只分單聲道和雙聲道,雙聲道便是立體聲                             

 本篇記錄ffmpeg+SDL播放音頻,其中會在特定狀況下使用libswresample進行音頻重採樣。佈局

流程圖

代碼示例

主線程只負責讀AVPacket存到隊列。—>av_read_frame()。ui

其餘全部的解碼,輸出工做都由callback完成。spa

  • callback中從隊列中取AVPacketList,再把AVPacketList中的AVPacket拿出來解碼。
  • 解碼後放到緩衝區,而後輸出。
  • 一次調用callback,就輸出長度爲len的數據(callback第三個參數,通常爲2048),很少很多。
  • 當緩衝區沒有數據的時候,就去解碼,放到緩衝區,再輸出。
  • 當解碼的數據多於len的時候,就只處理len個,其餘的留給後續的callback。
  • callback函數會一次次調用,直到全部處理完。

這其中,當解碼完一幀,須要拿這一幀的參數去和SDL設置的參數進行對比,若是有參數不一致,則須要進行重採樣,不然就會出現雜音。線程

具體見代碼註釋。 code

代碼量較多,可到文末的下載地址就行下載。

play_audio.c

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/samplefmt.h>
#include <libavutil/mathematics.h>

#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

#include <stdio.h>

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

#define FIX_INPUT 1

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

PacketQueue audioq;

int quit = 0;

AVFrame wanted_frame;

void packet_queue_init (PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));
    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 = 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;
    }

    q->last_pkt = pkt1;
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);

    return 0;
}

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

    for(;;)
    {

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

        pkt1 = q->first_pkt;
        if(pkt1)
        {
            q->first_pkt = pkt1->next;
            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 int audio_pkt_size = 0;
    static AVFrame frame;

    int len1, data_size = 0;

    SwrContext *swr_ctx = NULL;

    int resampled_data_size;

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


        if(quit)
        {
            return -1;
        }

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

        audio_pkt_size = pkt.size;

        while(audio_pkt_size > 0)
        {

            int got_frame = 0;
            len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);

            if(len1 < 0)
            {
                audio_pkt_size = 0;
                break;
            }

            audio_pkt_size -= len1;

            if(!got_frame)
                continue;

            data_size = av_samples_get_buffer_size(NULL, aCodecCtx->channels, frame.nb_samples,
                                                   AUDIO_S16SYS, 1);

            if (frame.channels > 0 && frame.channel_layout == 0)
                frame.channel_layout = av_get_default_channel_layout(frame.channels);
            else if (frame.channels == 0 && frame.channel_layout > 0)
                frame.channels = av_get_channel_layout_nb_channels(frame.channel_layout);

            /**
             * 接下來判斷咱們以前設置SDL時設置的聲音格式(AV_SAMPLE_FMT_S16),聲道佈局,
             * 採樣頻率,每一個AVFrame的每一個聲道採樣數與
             * 獲得的該AVFrame分別是否相同,若有任意不一樣,咱們就須要swr_convert該AvFrame,
             * 而後才能符合以前設置好的SDL的須要,才能播放
             */
            if(frame.format != AUDIO_S16SYS
                    || frame.channel_layout != aCodecCtx->channel_layout
                    || frame.sample_rate != aCodecCtx->sample_rate
                    || frame.nb_samples != SDL_AUDIO_BUFFER_SIZE)
            {

                if (swr_ctx != NULL)
                {
                    swr_free(&swr_ctx);
                    swr_ctx = NULL;
                }

                swr_ctx = swr_alloc_set_opts(NULL, wanted_frame.channel_layout, (enum AVSampleFormat)wanted_frame.format, wanted_frame.sample_rate,
                                             frame.channel_layout, (enum AVSampleFormat)frame.format, frame.sample_rate, 0, NULL);

                if (swr_ctx == NULL || swr_init(swr_ctx) < 0)
                {
                    fprintf(stderr, "swr_init failed: \n" );
                    break;
                }
            }

            if(swr_ctx)
            {
                int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame.sample_rate) + frame.nb_samples,
                                                    wanted_frame.sample_rate, wanted_frame.format, AV_ROUND_INF);
                
                /**
                  * 轉換該AVFrame到設置好的SDL須要的樣子,有些舊的代碼示例最主要就是少了這一部分,
                  * 每每一些音頻能播,一些不能播,這就是緣由,好比有些源文件音頻恰巧是AV_SAMPLE_FMT_S16的。
                  * swr_convert 返回的是轉換後每一個聲道(channel)的採樣數
                  */
                int len2 = swr_convert(swr_ctx, &audio_buf, dst_nb_samples,(const uint8_t**)frame.data, frame.nb_samples);
                if (len2 < 0)
                {
                    fprintf(stderr, "swr_convert failed \n" );
                    break;
                }

                resampled_data_size = wanted_frame.channels * len2 * av_get_bytes_per_sample((enum AVSampleFormat)wanted_frame.format);
            }else{
                resampled_data_size = data_size;
            }



            return resampled_data_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;

    SDL_memset(stream, 0, len);

    //向設備發送長度爲len的數據
    while(len > 0)
    {
        //緩衝區中無數據
        if(audio_buf_index >= audio_buf_size)
        {
            //從packet中解碼數據
            audio_size = audio_decode_frame(aCodecCtx, audio_buf, audio_buf_size);
            
            if(audio_size < 0) //沒有解碼到數據或者出錯,填充0
            {
                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;

        SDL_MixAudio(stream, audio_buf + audio_buf_index, len1, SDL_MIX_MAXVOLUME);

        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
}


int main(int argc, char *argv[])
{
    AVFormatContext *pFormatCtx = NULL;
    int             i, audioStream;

    AVPacket         packet;

    AVCodecContext  *aCodecCtx = NULL;
    AVCodec         *aCodec = NULL;

    SDL_AudioSpec   wanted_spec, spec;

    SDL_Event       event;

    char filename[100];

#ifdef FIX_INPUT
    strcpy(filename, "/home/wanghuatian/oceans.mp4");
#else
    if(argc < 2)
    {
        fprintf(stderr, "Usage: test <file> \n");
        exit(1);
    }

    strcpy(filename, argv[1]);
#endif // FIX_INPUT



    av_register_all();

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        fprintf(stderr,"Could not initialize SDL - %s " + *SDL_GetError());
        exit(1);
    }

    // 讀取文件頭,將格式相關信息存放在AVFormatContext結構體中
    if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
        return -1;
    // 檢測文件的流信息
    if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
        return -1;

    // 在控制檯輸出文件信息
    av_dump_format(pFormatCtx, 0, filename, 0);

    for(i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
            audioStream = i;
    }

    aCodecCtx = pFormatCtx->streams[audioStream]->codec;

    aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
    if(!aCodec)
    {
        fprintf(stderr, "Unsupported codec ! \n");
        return -1;
    }

    wanted_spec.freq = aCodecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = aCodecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = aCodecCtx;


    /**
     *SDL_OpenAudio 函數經過wanted_spec來打開音頻設備,成功返回零,將實際的硬件參數傳遞給spec的指向的結構體。
     *若是spec爲NULL,audio data將經過callback函數,保證將自動轉換成硬件音頻格式。
     *
     *音頻設備剛開始播放靜音,當callback變得可用時,經過調用SDL_PauseAudio(0)來開始播放。
     *因爲audio diver 可能修改音頻緩存的請求大小,因此你應該申請任何的混合緩存(mixing buffers),在你打開音頻設備以後。*/
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0)
    {
        fprintf(stderr, "SDL_OpenAudio: %s \n", SDL_GetError());
        return -1;
    }

    wanted_frame.format = AV_SAMPLE_FMT_S16;
    wanted_frame.sample_rate = spec.freq;
    wanted_frame.channel_layout = av_get_default_channel_layout(spec.channels);
    wanted_frame.channels = spec.channels;

    avcodec_open2(aCodecCtx, aCodec, NULL);

    packet_queue_init(&audioq);
    SDL_PauseAudio(0);


    while (av_read_frame(pFormatCtx, &packet) >= 0)
    {
        if (packet.stream_index == audioStream)
            packet_queue_put(&audioq, &packet);
        else
            av_free_packet(&packet);

        SDL_PollEvent(&event);

        switch (event.type) {

            case SDL_QUIT:
                quit = 1;
                SDL_Quit();
                exit(0);
                break;

            default:
                break;
        }
    }

    getchar();
   
    return 0;
}

編譯

gcc -o play_audio play_audio.c -lavutil -lavformat -lavcodec -lavutil -lswscale -lswresample -lSDL

運行

./play_audio 文件路徑+文件名

下載地址

https://github.com/Huatian/ffmpeg-tutorial

相關文章
相關標籤/搜索