1. 前言

目前为止,学习了并记录了ffmpeg+SDL2显示视频以及事件(event)的内容。 
这篇中记录ffmpeg+SDL2播放音频,没加入事件处理。 
接下来加入事件处理并继续学习音视频同步,再接下来就添加暂停之类的或者添个界面。

2. 流程图

3. 示例

示例代码的主要思想是:(和音频播放器V1.0思想一样,实现不同。不同在于这个程序用一个队列存储主线程读到的AVPacket)

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

详见代码及代码中注释

注意:

  • 链表结构是AVPacketList,含两个成员: 
    • AVPacket packet;
    • AVPacketList *next;
  • 代码中注释掉的代码(块注释的),是不用也可以正常运行的,要可能更健壮之类的。

3.1 代码

(所有代码都贴了,较长,后面有代码下载地址)

  • audio_player_v2.0.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h> #define __STDC_CONSTANT_MACROS //ffmpeg要求 #ifdef __cplusplus
extern "C"
{
#endif #include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h> #ifdef __cplusplus
}
#endif //
#include "wrap_base.h"
#include "packet_queue.h" #define AVCODE_MAX_AUDIO_FRAME_SIZE 192000 //1 second of 48khz 32bit audio
#define SDL_AUDIO_BUFFER_SIZE 1024 // #define FILE_NAME "/home/isshe/Music/WavinFlag.aac"
#define ERR_STREAM stderr
#define OUT_SAMPLE_RATE 44100 AVFrame wanted_frame;
PacketQueue audio_queue;
int quit = 0; void audio_callback(void *userdata, Uint8 *stream, int len);
int audio_decode_frame(AVCodecContext *pcodec_ctx, uint8_t *audio_buf, int buf_size);
/*
void packet_queue_init(PacketQueue *queue);
int packet_queue_put(PacketQueue *queue, AVPacket *packet);
int packet_queue_get(PacketQueue *queue, AVPacket *packet, int block);
*/
int main(int argc, char *argv[])
{
AVFormatContext *pformat_ctx = NULL;
int audio_stream = -1;
AVCodecContext *pcodec_ctx = NULL;
AVCodecContext *pcodec_ctx_cp = NULL;
AVCodec *pcodec = NULL;
AVPacket packet ; //!
AVFrame *pframe = NULL;
char filename[256] = FILE_NAME; //SDL
SDL_AudioSpec wanted_spec;
SDL_AudioSpec spec ; //ffmpeg 初始化
av_register_all(); //SDL初始化
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
fprintf(ERR_STREAM, "Couldn't init SDL:%s\n", SDL_GetError());
exit(-1);
} get_file_name(filename, argc, argv); //打开文件
if (avformat_open_input(&pformat_ctx, filename, NULL, NULL) != 0)
{
fprintf(ERR_STREAM, "Couldn't open input file\n");
exit(-1);
} //检测文件流信息
//旧版的是av_find_stream_info()
if (avformat_find_stream_info(pformat_ctx, NULL) < 0)
{
fprintf(ERR_STREAM, "Not Found Stream Info\n");
exit(-1);
} //显示文件信息,十分好用的一个函数
av_dump_format(pformat_ctx, 0, filename, false); //找视音频流
if (find_stream_index(pformat_ctx, NULL, &audio_stream) == -1)
{
fprintf(ERR_STREAM, "Couldn't find stream index\n");
exit(-1);
}
printf("audio_stream = %d\n", audio_stream); //找到对应的解码器
pcodec_ctx = pformat_ctx->streams[audio_stream]->codec;
pcodec = avcodec_find_decoder(pcodec_ctx->codec_id);
if (!pcodec)
{
fprintf(ERR_STREAM, "Couldn't find decoder\n");
exit(-1);
} /*
pcodec_ctx_cp = avcodec_alloc_context3(pcodec);
if (avcodec_copy_context(pcodec_ctx_cp, pcodec_ctx) != 0)
{
fprintf(ERR_STREAM, "Couldn't copy codec context\n");
exit(-1);
}
*/ //设置音频信息, 用来打开音频设备。
wanted_spec.freq = pcodec_ctx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = pcodec_ctx->channels; //通道数
wanted_spec.silence = 0; //设置静音值
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; //读取第一帧后调整?
wanted_spec.callback = audio_callback;
wanted_spec.userdata = pcodec_ctx; //wanted_spec:想要打开的
//spec: 实际打开的,可以不用这个,函数中直接用NULL,下面用到spec的用wanted_spec代替。
//这里会开一个线程,调用callback。
//SDL_OpenAudio->open_audio_device(开线程)->SDL_RunAudio->fill(指向callback函数)
//可以用SDL_OpenAudioDevice()代替
if (SDL_OpenAudio(&wanted_spec, &spec) < 0)
{
fprintf(ERR_STREAM, "Couldn't open Audio:%s\n", SDL_GetError());
exit(-1);
} //设置参数,供解码时候用, swr_alloc_set_opts的in部分参数
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; //初始化AVCondecContext,以及进行一些处理工作。
avcodec_open2(pcodec_ctx, pcodec, NULL); //初始化队列
packet_queue_init(&audio_queue); //可以用SDL_PauseAudioDevice()代替,目前用的这个应该是旧的。
//0是不暂停,非零是暂停
//如果没有这个就放不出声音
SDL_PauseAudio(0); //为什么要这个? //读一帧数据
while(av_read_frame(pformat_ctx, &packet) >= 0) //读一个packet,数据放在packet.data
{
if (packet.stream_index == audio_stream)
{
packet_queue_put(&audio_queue, &packet);
}
else
{
av_free_packet(&packet);
}
}
getchar(); //...
return 0;
} //注意userdata是前面的AVCodecContext.
//len的值常为2048,表示一次发送多少。
//audio_buf_size:一直为样本缓冲区的大小,wanted_spec.samples.(一般每次解码这么多,文件不同,这个值不同)
//audio_buf_index: 标记发送到哪里了。
//这个函数的工作模式是:
//1. 解码数据放到audio_buf, 大小放audio_buf_size。(audio_buf_size = audio_size;这句设置)
//2. 调用一次callback只能发送len个字节,而每次取回的解码数据可能比len大,一次发不完。
//3. 发不完的时候,会len == 0,不继续循环,退出函数,继续调用callback,进行下一次发送。
//4. 由于上次没发完,这次不取数据,发上次的剩余的,audio_buf_size标记发送到哪里了。
//5. 注意,callback每次一定要发且仅发len个数据,否则不会退出。
//如果没发够,缓冲区又没有了,就再取。发够了,就退出,留给下一个发,以此循环。
//三个变量设置为static就是为了保存上次数据,也可以用全局变量,但是感觉这样更好。
void audio_callback(void *userdata, Uint8 *stream, int len)
{
AVCodecContext *pcodec_ctx = (AVCodecContext *)userdata;
int len1 = 0;
int audio_size = 0; //注意是static
//为什么要分那么大?
static uint8_t audio_buf[(AVCODE_MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0; //初始化stream,每次都要。
SDL_memset(stream, 0, len); while(len > 0)
{
if (audio_buf_index >= audio_buf_size)
{
//数据全部发送,再去获取
//自定义的一个函数
audio_size = audio_decode_frame(pcodec_ctx, audio_buf, sizeof(audio_buf));
if (audio_size < 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;
// printf("len1 = %d\n", len1);
if (len1 > len) //len1常比len大,但是一个callback只能发len个
{
len1 = len;
} //新程序用 SDL_MixAudioFormat()代替
//混合音频, 第一个参数dst, 第二个是src,audio_buf_size每次都在变化
SDL_MixAudio(stream, (uint8_t*)audio_buf + audio_buf_index, len, SDL_MIX_MAXVOLUME);
//
//memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
} } //对于音频来说,一个packet里面,可能含有多帧(frame)数据。 int audio_decode_frame(AVCodecContext *pcodec_ctx,
uint8_t *audio_buf, int buf_size)
{
AVPacket packet;
AVFrame *frame;
int got_frame;
int pkt_size = 0;
// uint8_t *pkt_data = NULL;
int decode_len;
int try_again = 0;
long long audio_buf_index = 0;
long long data_size = 0;
SwrContext *swr_ctx = NULL;
int convert_len = 0;
int convert_all = 0; if (packet_queue_get(&audio_queue, &packet, 1) < 0)
{
fprintf(ERR_STREAM, "Get queue packet error\n");
return -1;
} // pkt_data = packet.data;
pkt_size = packet.size;
// fprintf(ERR_STREAM, "pkt_size = %d\n", pkt_size); frame = av_frame_alloc();
while(pkt_size > 0)
{
// memset(frame, 0, sizeof(AVFrame));
//pcodec_ctx:解码器信息
//frame:输出,存数据到frame
//got_frame:输出。0代表有frame取了,不意味发生了错误。
//packet:输入,取数据解码。
decode_len = avcodec_decode_audio4(pcodec_ctx,
frame, &got_frame, &packet);
if (decode_len < 0) //解码出错
{
//重解, 这里如果一直<0呢?
fprintf(ERR_STREAM, "Couldn't decode frame\n");
if (try_again == 0)
{
try_again++;
continue;
}
try_again = 0;
} if (got_frame)
{ /* //用定的音频参数获取样本缓冲区大小
data_size = av_samples_get_buffer_size(NULL,
pcodec_ctx->channels, frame->nb_samples,
pcodec_ctx->sample_fmt, 1); assert(data_size <= buf_size);
// memcpy(audio_buf + audio_buf_index, frame->data[0], data_size);
*/
//chnanels: 通道数量, 仅用于音频
//channel_layout: 通道布局。
//多音频通道的流,一个通道布局可以具体描述其配置情况.通道布局这个概念不懂。
//大概指的是单声道(mono),立体声道(stereo), 四声道之类的吧?
//详见源码及:https://xdsnet.gitbooks.io/other-doc-cn-ffmpeg/content/ffmpeg-doc-cn-07.html#%E9%80%9A%E9%81%93%E5%B8%83%E5%B1%80 if (frame->channels > 0 && frame->channel_layout == 0)
{
//获取默认布局,默认应该了stereo吧?
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);
} if (swr_ctx != NULL)
{
swr_free(&swr_ctx);
swr_ctx = NULL;
} //设置common parameters
//2,3,4是output参数,4,5,6是input参数。
swr_ctx = swr_alloc_set_opts(NULL, wanted_frame.channel_layout,
(AVSampleFormat)wanted_frame.format,
wanted_frame.sample_rate, frame->channel_layout,
(AVSampleFormat)frame->format, frame->sample_rate, 0, NULL);
//初始化
if (swr_ctx == NULL || swr_init(swr_ctx) < 0)
{
fprintf(ERR_STREAM, "swr_init error\n");

ffmpeg+SDL2实现的音频播放器V2.0(无杂音)的更多相关文章

  1. Movist for Mac(高清媒体播放器)v2.0.7中文特别版

    Movist for Mac中文破解版是目前Mac平台上最好用的视频播放器,功能强大简单好用.movist mac版拥有美观简洁的用户界面,提供多种功能,支持视频解码加速高品质的字幕,全屏幕浏览,是与 ...

  2. ffmpeg + sdl -03 简单音频播放器实现

    没办法,工作中遇到了问题. 目前NEC EMMA的架构如下: 从USB读入文件 -> 文件分析并提取Packet中的Payload Data   -> NEC HANDLE AVTrans ...

  3. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)

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

  4. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)

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

  5. 11.QT-ffmpeg+QAudioOutput实现音频播放器

    1.前言      由于QAudioOutput支持的输入数据必须是原始数据,所以播放mp3,WAV,AAC等格式文件,需要解封装后才能支持播放.      而在QT中,提供了QMediaPlayer ...

  6. HTML5 音频播放器-Javascript代码(短小精悍)

    直接上干货咯! //HTML5 音频播放器 lzpong 2015/01/19 var wavPlayer = function () { if(window.parent.wavPlayer) re ...

  7. IOS开发之简单音频播放器

    今天第一次接触IOS开发的UI部分,之前学OC的时候一直在模拟的使用Target-Action回调模式,今天算是真正的用了一次.为了熟悉一下基本控件的使用方法,和UI部分的回调,下面开发了一个特别简易 ...

  8. 【jquery】一款不错的音频播放器——Amazing Audio Player

    前段时间分享了一款视频播放器,点击这里.今天介绍一款不错的音频播放器——Amazing Audio Player. 介绍: Amazing Audio Player 是一个使用很方便的 Windows ...

  9. 与众不同 windows phone (14) - Media(媒体)之音频播放器, 视频播放器, 与 Windows Phone 的音乐和视频中心集成

    原文:与众不同 windows phone (14) - Media(媒体)之音频播放器, 视频播放器, 与 Windows Phone 的音乐和视频中心集成 [索引页][源码下载] 与众不同 win ...

随机推荐

  1. 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用

    背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...

  2. 定位 iframe

    定位iframe # 1.有id,并且唯一,直接写id driver.switch_to_frame("x-URS-iframe") driver.switch_to.frame( ...

  3. VMwareWorkstation如何设置共享文件夹

    首先需要安装VMware Tools 这个嘛,应该是需要安装的,之前没有安装好像就没有设置成功. 没有安装的参考如何安装VMware Tools 然后在虚拟机设置里面设置共享路径 右键虚拟机名称,打开 ...

  4. MTK Android Framework用SystemProperties通过JNI调用访问系统属性

    1.导包 import android.os.SystemProperties; 2. Android SystemProperties设置/读取 #设置 Systemproperties.set(n ...

  5. Python Requests-学习笔记(6)-响应头

    响应头 我们可以查看以一个Python字典形式展示的服务器响应头: import requestsurl = 'http://httpbin.org/post'r = requests.get(url ...

  6. thymeleaf的特殊属性赋值

    在用thymeleaf时,遇到特殊属性不知道该怎么解决如下: 问题1:循环时,遇到特殊的属性,不知道怎么赋值 如:cate-id="" ,fid=""; 使用t ...

  7. 修改vs默认浏览器

    右键你的Html或者网页项目,选择"使用以下工具浏览" 跳出选择框,选择你想要的浏览器作为默认值即可,也可以添加你想要的浏览器.

  8. Ant概念

    Ant是基于Java的.可以跨平台的项目编译和生成工具.

  9. MVC5+EasyUI+EF6增删改查的演示

    一.创建MVC项目 二.引入EasyUI 1.进入easyui官网下载源码 2. 将上述源码中需要的jquery 有选择的加到项目中来 添加Content文件夹,放入easyui代码 三.添加EF, ...

  10. mysql创建存储过程及调用

    创建存储过程简单示例: DELIMITER //CREATE PROCEDURE ccgc()BEGINSELECT * FROM TEXT;SELECT * FROM s_user;END//DEL ...