文档章节

ffmpeg+SDL播放音频(无杂音)

曾经花田错
 曾经花田错
发布于 2016/12/07 15:07
字数 1453
阅读 138
收藏 1

作者:Huatian

github:https://github.com/Huatian

邮箱: 773512457@qq.com

平台:Fedora 24 (64bit)

前言

音频常见概念:

  • 采样率 sample rate    表示以多快的速度来播放这段样本流,它的表示方式为每秒多少次采样
  • 采样精度                         每个采样 sample所用的二进制位数
  • 声道数                              一般只分单声道和双声道,双声道即是立体声                             

 本篇记录ffmpeg+SDL播放音频,其中会在特定情况下使用libswresample进行音频重采样。

流程图

代码示例

主线程只负责读AVPacket存到队列。—>av_read_frame()。

其他所有的解码,输出工作都由callback完成。

  • callback中从队列中取AVPacketList,再把AVPacketList中的AVPacket拿出来解码。
  • 解码后放到缓冲区,然后输出。
  • 一次调用callback,就输出长度为len的数据(callback第三个参数,一般为2048),不多不少。
  • 当缓冲区没有数据的时候,就去解码,放到缓冲区,再输出。
  • 当解码的数据多于len的时候,就只处理len个,其他的留给后续的callback。
  • callback函数会一次次调用,直到所有处理完。

这其中,当解码完一帧,需要拿这一帧的参数去和SDL设置的参数进行对比,如果有参数不一致,则需要进行重采样,否则就会出现杂音。

具体见代码注释。 

代码量较多,可到文末的下载地址就行下载。

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

© 著作权归作者所有

共有 人打赏支持
曾经花田错
粉丝 5
博文 42
码字总数 39130
作品 0
海淀
程序员
最简单的基于FFMPEG+SDL的音频播放器:拆分-解码器和播放器

===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: 《最简单的基于FFMPEG+SDL的音频播放器》 《最简单的基于FFMPEG+SDL的音频播放器 ve...

leixiaohua1020
2015/07/17
0
0
《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频

这两天开始带广播电视工程大二的暑假小学期的课程设计了。本次小学期课程内容为《基于 FFmpeg + SDL 的视频播放器的制作》,其中主要讲述了视音频开发的入门知识。由于感觉本课程的内容不但适...

leixiaohua1020
2015/07/26
0
0
5步告诉你QQ音乐的完美音质是怎么来的,播放器的秘密都在这里

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由QQ音乐技术团队发表于云+社区专栏 一、问题背景与分析 不久前,团队发现其Android平台App在播放MV视频《凤凰花开的路口》时...

腾讯云+社区
10/10
0
0
android开发,怎样处理音频流的杂音?

音频通过手机的麦克风即时播放,出现很大的杂音,该怎样处理,使杂音变小?主要是想实现直接用手机麦克风来达到唱歌的效果……

MarsNavy
2012/08/20
2.8K
1
IOS 资料备份

利用本地服务器边下载视频边播放 目前还没有做好,下面是参考资料,做个备份; 参考资料: http://blog.csdn.net/wxw55/article/details/17557295 http://www.code4app.com/ios/视频边下载边...

xiaobai1315
2016/10/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

可爱的python测试开发库(python测试开发工具库汇总)

欢迎转载,转载请注明来源: github地址 谢谢点赞 本文地址 相关书籍下载 测试开发 Web UI测试自动化 splinter - web UI测试工具,基于selnium封装。 链接 selenium - web UI自动化测试。 链...

python测试开发人工智能安全
54分钟前
2
0
Shiro | 实现权限验证完整版

写在前面的话 提及权限,就会想到安全,是一个十分棘手的话题。这里只是作为学校Shiro的一个记录,而不是,权限就应该这样设计之类的。 Shiro框架 1、Shiro是基于Apache开源的强大灵活的开源...

冯文议
今天
1
0
linux 系统的运行级别

运行级别 运行级别 | 含义 0 关机 1 单用户模式,可以想象为windows 的安全模式,主要用于修复系统 2 不完全的命令模式,不含NFS服务 3 完全的命令行模式,就是标准的字符界面 4 系统保留 5 ...

Linux学习笔记
今天
2
0
学习设计模式——命令模式

任何模式的出现,都是为了解决一些特定的场景的耦合问题,以达到对修改封闭,对扩展开放的效果。命令模式也不例外: 命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。 解决了这...

江左煤郎
今天
3
0
字典树收集(非线程安全,后续做线程安全改进)

将500W个单词放进一个数据结构进行存储,然后进行快速比对,判断一个单词是不是这个500W单词之中的;来了一个单词前缀,给出500w个单词中有多少个单词是该前缀. 1、这个需求首先需要设计好数据结...

算法之名
昨天
15
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部