[5] ffmpeg+SDL2实现的音频播放器V2.0(无杂音)
2016-10-05 01:31
435 查看
日期:2016.10.4
作者:isshe
github:github.com/isshe
邮箱:i.sshe@outlook.com
平台:ubuntu16.04 64bit
这篇中记录ffmpeg+SDL2播放音频,没加入事件处理。
接下来加入事件处理并继续学习音视频同步,再接下来就添加暂停之类的或者添个界面。
主线程只负责读AVPacket存到队列。—>av_read_frame()
其他所有的解码,输出工作都由callback完成。
callback中从队列中取AVPacketList,再把AVPacketList中的AVPacket拿出来解码。
解码后放到缓冲区,然后输出。
一次调用callback,就输出长度为len的数据(callback第三个参数,一般为2048),不多不少。
当缓冲区没有数据的时候,就去解码,放到缓冲区,再输出。
当解码的数据多于len的时候,就只处理len个,其他的留给后续的callback。
callback函数会一次次调用,直到所有处理完。
详见代码及代码中注释
注意:
链表结构是AVPacketList,含两个成员:
AVPacket packet;
AVPacketList *next;
代码中注释掉的代码(块注释的),是不用也可以正常运行的,要可能更健壮之类的。
audio_player_v2.0.c
packet_queue.h
packet_queue.c
wrap_base.h
wrap_base.c
或者
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
另一入门经典:http://dranger.com/ffmpeg/tutorial01.html
SDL手册:http://wiki.libsdl.org/SDL_OpenAudioDevice
作者:isshe
github:github.com/isshe
邮箱:i.sshe@outlook.com
平台:ubuntu16.04 64bit
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"); break; } //av_rescale_rnd(): 用指定的方式队64bit整数进行舍入(rnd:rounding), //使如a*b/c之类的操作不会溢出。 //swr_get_delay(): 返回 第二个参数分之一(下面是:1/frame->sample_rate) //AVRouding是一个enum,1的意思是round away from zero. /* 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, AVRounding(1)); */ //转换音频。把frame中的音频转换后放到audio_buf中。 //第2,3参数为output, 第4,5为input。 //可以使用#define AVCODE_MAX_AUDIO_FRAME_SIZE 192000 //把dst_nb_samples替换掉, 最大的采样频率是192kHz. convert_len = swr_convert(swr_ctx, &audio_buf + audio_buf_index, AVCODE_MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame->data, frame->nb_samples); printf("decode len = %d, convert_len = %d\n", decode_len, convert_len); //解码了多少,解码到了哪里 // pkt_data += decode_len; pkt_size -= decode_len; //转换后的有效数据存到了哪里,又audio_buf_index标记 audio_buf_index += convert_len;//data_size; //返回所有转换后的有效数据的长度 convert_all += convert_len; } } return wanted_frame.channels * convert_all * av_get_bytes_per_sample((AVSampleFormat)wanted_frame.format); // return audio_buf_index; }
packet_queue.h
#ifndef PACKET_QUEUE_H_ #define PACKET_QUEUE_H_ #ifdef __cplusplus extern "C"{ #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> #include <SDL2/SDL.h> typedef struct PacketQueue { AVPacketList *first_pkt; //队头的一个packet, 注意类型不是AVPacket AVPacketList *last_pkt; //队尾packet int nb_packets; // paket个数 int size; // SDL_mutex *mutex; // SDL_cond *cond; // 条件变量 }PacketQueue; void packet_queue_init(PacketQueue *queue); int packet_queue_put(PacketQueue *queue, AVPacket *packet); int packet_queue_get(PacketQueue *queue, AVPacket *pakcet, int block); #ifdef __cplusplus } #endif #endif
packet_queue.c
#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 "packet_queue.h" /*======================================================================\ * Author (作者): i.sshe * Date (日期): 2016/10/03 * Others (其他): 初始化队列 \*=======================================================================*/ void packet_queue_init(PacketQueue *queue) { // memset(queue, 0, sizeof(PacketQueue)); queue->first_pkt = NULL; queue->last_pkt = NULL; queue->mutex = SDL_CreateMutex(); queue->cond = SDL_CreateCond(); } /*======================================================================\ * Author (作者): i.sshe * Date (日期): 2016/10/03 * Others (其他): 入队 \*=======================================================================*/ int packet_queue_put(PacketQueue *queue, AVPacket *packet) { AVPacketList *pkt_list; // ??? if (av_dup_packet(packet) < 0) { return -1; } pkt_list = (AVPacketList *)av_malloc(sizeof(AVPacketList)); if (pkt_list == NULL) { return -1; } pkt_list->pkt = *packet; pkt_list->next = NULL; //上锁 SDL_LockMutex(queue->mutex); if (queue->last_pkt == NULL) //空队 { queue->first_pkt = pkt_list; } else { queue->last_pkt->next = pkt_list; } queue->last_pkt = pkt_list; //这里queue->last_pkt = queue->last_pkt->next 的意思,但是,处理了更多的情况。 queue->nb_packets++; queue->size += packet->size; SDL_CondSignal(queue->cond); //??? SDL_UnlockMutex(queue->mutex); return 0; } /*======================================================================\ * Author (作者): i.sshe * Date (日期): 2016/10/03 * Others (其他): 出队 \*=======================================================================*/ extern int quit; int packet_queue_get(PacketQueue *queue, AVPacket *pkt, int block) { AVPacketList *pkt_list = NULL; int ret = 0; SDL_LockMutex(queue->mutex); while(1) { if (quit) { ret = -1; break; } pkt_list = queue->first_pkt; if (pkt_list != NULL) //队不空,还有数据 { queue->first_pkt = queue->first_pkt->next; //pkt_list->next if (queue->first_pkt == NULL) { queue->last_pkt = NULL; } queue->nb_packets--; queue->size -= pkt_list->pkt.size; *pkt = pkt_list->pkt; // 复制给packet。 av_free(pkt_list); ret = 1; break; } else if (block == 0) { ret = 0; break; } else { SDL_CondWait(queue->cond, queue->mutex); } } SDL_UnlockMutex(queue->mutex); return ret; }
wrap_base.h
#ifndef WRAP_BASE_H_ #define WRAP_BASE_H_ #ifdef __cplusplus extern "C"{ #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> #include <SDL2/SDL.h> int find_stream_index(AVFormatContext *pformat_ctx, int *video_stream, int *audio_stream); void get_file_name(char *filename, int argc, char *argv[]); #ifdef __cplusplus } #endif #endif
wrap_base.c
#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> #include <assert.h> #include "wrap_base.h" #ifdef __cplusplus } #endif /*======================================================================\ * Author (作者): i.sshe * Date (日期): 2016/10/03 * Others (其他): 成功返回 \*=======================================================================*/ int find_stream_index(AVFormatContext *pformat_ctx, int *video_stream, int *audio_stream) { assert(video_stream != NULL || audio_stream != NULL); int i = 0; int audio_index = -1; int video_index = -1; for (i = 0; i < pformat_ctx->nb_streams; i++) { if (pformat_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { video_index = i; } if (pformat_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { audio_index = i; } } //注意以下两个判断有可能返回-1. if (video_stream == NULL) { *audio_stream = audio_index; return *audio_stream; } if (audio_stream == NULL) { *video_stream = video_index; return *video_stream; } *video_stream = video_index; *audio_stream = audio_index; return 0; } /*======================================================================\ * Author (作者): i.sshe * Date (日期): 2016/10/03 * Others (其他): 获取文件名 \*=======================================================================*/ void get_file_name(char *filename, int argc, char *argv[]) { if (argc == 2) { memcpy(filename, argv[1], strlen(argv[1])+1); } else if (argc > 2) { fprintf(stderr, "Usage: ./*.out filename\n"); exit(-1); } }
3.2 编译
Mackfile(蹩脚的)CC = g++ SOURCE = *.c TARGET = audio_player_v1.2.out LIBDIR = -L/usr/local/lib INCDIR = -I/usr/local/include/ -I../../common/ OPTION = -O2 -Wall -g LIB_FFMPEG = -lavformat -lavcodec -lavformat -lavutil -lswresample -lswscale LIB_SDL2 = -lSDL2 -lSDL2main LIB_OTHER = -lx264 -lx265 -lvpx -lmp3lame -lopus -lfdk-aac -lX11 -lva -lvdpau -lva-drm -lva-x11 -lvorbisenc -lvorbis -ltheoraenc -ltheoradec -ldl -lm -lpthread -lz all: $(CC) $(SOURCE) -o $(TARGET) $(LIBDIR) $(INCDIR) $(OPTION) $(LIB_FFMPEG) $(LIB_OTHER) $(LIB_SDL2) clean: rm -f *.out
3.3 运行
./filename.out
或者
./filename.outaudiofile
4. 参考资料
书籍: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
另一入门经典:http://dranger.com/ffmpeg/tutorial01.html
SDL手册:http://wiki.libsdl.org/SDL_OpenAudioDevice
5. 代码下载
http://download.csdn.net/detail/i_scream_/9645700相关文章推荐
- [4] ffmpeg + SDL2 实现的有杂音的音频播放器
- 制造自己的wave音频播放器-使用waveOutOpen与waveOutWrite实现
- web播放器插件代码 实现一个在线查听音频文件的功能
- 制造自己的wave音频播放器-使用waveOutOpen与waveOutWrite实现
- 使用FFMPEG实现音频播放器
- MTK 音频播放器的实现
- EasyPlayerPro windows播放器本地音频播放音量控制实现
- Html5之高级-5 HTML5音频处理(在H5中播放音频、编程实现音频播放器)
- 实例解析使用Java实现基本的音频播放器的编写要点
- HTML5音频播放器显示歌词功能思路及实现
- ffmpeg + sdl -03 简单音频播放器实现
- iOS音频播放 (六):简单的音频播放器实现
- ffmpeg学习(三)——ffmpeg+SDL2 实现简单播放器
- Android音频播放器的实现
- iOS音频播放 (六):简单的音频播放器实现
- 制造自己的wave音频播放器-使用waveOutOpen与waveOutWrite实现
- 基于ffmpeg+SDL2 实现简单rtsp播放器
- HTML5实现Winamp2.9音频播放器插件
- android实现简单音频播放器
- ffmpeg+SDL2 实现简单播放器