最近在做播放器软件,接触到FFMEPEG的采集部分,中间也走了很多弯路,记录下,也给后来者插个眼

首先,初始化FFMEPEG和SDL,我这里使用的方法是视频数据上抛到接口,音频数据则通过SDL直接播放

FFMEPEG及SDL初始化

 int initFFmpeg() {//初始化设备号if (!pAVFormatContext) {pAVFormatContext = avformat_alloc_context(); //申请一个AVFormatContext结构的内存,并进行简单初始化}pAVFormatContext->interrupt_callback.callback = decode_interrupt_cb;pAVFormatContext->interrupt_callback.opaque = this;if (!pAVFrame) {pAVFrame = av_frame_alloc();}AVDictionary *avdic = NULL;//av_dict_set(&avdic, "bufsize", "2000k", 0);if (rtspTcp_) {av_dict_set(&avdic, "rtsp_transport", "tcp", 0);}lasttime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();//打开视频流int result = avformat_open_input(&pAVFormatContext, url_.c_str(), NULL, &avdic);if (avdic) {av_dict_free(&avdic);avdic = NULL;}if (result < 0) {//"打开视频流失败";return -1;}//获取视频流信息result = avformat_find_stream_info(pAVFormatContext, NULL);if (result < 0) {//"获取视频流信息失败";return -2;}av_dump_format(pAVFormatContext, 0, url_.c_str(), 0);//获取视频流索引videoStreamIndex = -1;for (int i = 0; i < pAVFormatContext->nb_streams; i++) {if (pAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {videoStreamIndex = i;break;}}if (videoStreamIndex == -1) {//"获取视频流索引失败";return -3;}//获取视频流的分辨率大小pAVCodecContext = avcodec_alloc_context3(NULL);if (!pAVCodecContext){return -4;}result = avcodec_parameters_to_context(pAVCodecContext, pAVFormatContext->streams[videoStreamIndex]->codecpar);if (result < 0) {return -5;}videoWidth = pAVCodecContext->width;videoHeight = pAVCodecContext->height;mtx_.lock();frame_.width = videoWidth;frame_.height = videoHeight;frame_.fmt = VideoFormat::VIDEO_FMT_I420;mtx_.unlock();//获取视频流解码器pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);//打开对应解码器result = avcodec_open2(pAVCodecContext, pAVCodec, NULL);if (result < 0) {//打开解码器失败return -6;}audioStreamIndex = -1;for (int i = 0; i < pAVFormatContext->nb_streams; i++) {if (pAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {audioStreamIndex = i;break;}}//将音频流信息拷贝到新的AVCodecContextAVCodecContext* pCodecCtxOrg = nullptr;pCodecCtxOrg = pAVFormatContext->streams[audioStreamIndex]->codec; // codec context// 找到audio stream的 decoderpAuCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);if (!pAuCodec){cout << "Unsupported codec!" << endl;return -1;}// 不直接使用从AVFormatContext得到的CodecContext,要复制一个pAUCodecContext = avcodec_alloc_context3(pAuCodec);if (avcodec_copy_context(pAUCodecContext, pCodecCtxOrg) != 0){cout << "Could not copy codec context!" << endl;return -1;}result = avcodec_open2(pAUCodecContext, pAuCodec, NULL);if (result < 0) {//打开解码器失败return -6;}//SDL初始化if (SDL_Init(SDL_INIT_AUDIO)){return -1;}//重采样SwrContext初始化resampler = swr_alloc_set_opts(NULL,pAUCodecContext->channel_layout,AV_SAMPLE_FMT_S16,44100,pAUCodecContext->channel_layout,pAUCodecContext->sample_fmt,pAUCodecContext->sample_rate,0,NULL);swr_init(resampler);//打开设备,使用的时SDL_OpenAudioDevice而不是SDL_OpenAudio.为了后续的音频设备采样播出//其中SDL_AudioSpec的回调callback设置为空,由系统自动采集音频数据并播出,而不是自己处理SDL_AudioSpec want, have;SDL_zero(want);SDL_zero(have);want.freq = 44100;want.channels = pAUCodecContext->channels;want.format = AUDIO_S16SYS;audioDeviceNum = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);SDL_PauseAudioDevice(audioDeviceNum, 0);return 0;}

其中包含了FFMEPEG对音频和视频数据的初始化内容,这部分与其他文章并无不同,就略过了

SDL初始化,我只是用了音频部分,需要注意的点,主要实在打开音频设备这一步上,

在SDL2中,共提供了两套用于播放音频数据的API:

SDL_AudioSpec 打开音频设备 数据播放 数据填充
第一套 callback = mCallback SDL_OpenAudio SDL_PauseAudio SDL_AudioCallback
第二套 callback = NULL SDL_OpenAudioDevice SDL_PauseAudioDevice SDL_QueueAudio

两套API,同时使用时混音处理会出现问题,无法正常播放,若有信心解决,可混用,否则不建议

区别:

单路流时,没有显著区别,均可正常使用,其中第一套需手动完成回调部分,实现数据传入音频设备,写入时,需调用SDL_MixAudio。

多路流时,SDL_OpenAudio只能打开一次,再次调用无条件返回错误,所以除第一次打开外,后续流仍需调用SDL_OpenAudioDevice,且同时存在时,SDL_PauseAudio和SDL_CloseAudio调用会对其他对应的音频设备造成影响。

数据填充:

我用的是第二套方案,以实现多路音频流播放,如需第一套实现,请自行查找

void loop() {std::unique_lock<mutex> lck(loopMtx_);running_ = true;   //开始运行int result = initFFmpeg();if (result < 0) {//初始化失败running_ = false;return;}int ret = 0;AVFrame* frame = av_frame_alloc();AVFrame* audioframe = av_frame_alloc();while (running_){uint64_t ts = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();if (av_read_frame(pAVFormatContext, &avPacket) >= 0) {if (avPacket.stream_index == videoStreamIndex) {//视频部分忽略}else if (avPacket.stream_index == audioStreamIndex){AVStream* stream = pAVFormatContext->streams[avPacket.stream_index];if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {ret = avcodec_send_packet(pAUCodecContext, &avPacket);while (ret >= 0) {ret = avcodec_receive_frame(pAUCodecContext, frame);if (ret >= 0) {int dst_samples = frame->channels * av_rescale_rnd(swr_get_delay(resampler, frame->sample_rate)+ frame->nb_samples,44100,frame->sample_rate,AV_ROUND_UP);uint8_t* audiobuf = NULL;ret = av_samples_alloc(&audiobuf,NULL,1,dst_samples,AV_SAMPLE_FMT_S16,1);dst_samples = frame->channels * swr_convert(resampler,&audiobuf,dst_samples,(const uint8_t**)frame->data,frame->nb_samples);ret = av_samples_fill_arrays(audioframe->data,audioframe->linesize,audiobuf,1,dst_samples,AV_SAMPLE_FMT_S16,1);SDL_QueueAudio(audioDeviceNum,audioframe->data[0],audioframe->linesize[0]);}}}}}av_packet_unref(&avPacket);//释放资源,否则内存会一直上升}}
};

通过FFMEPEG获取音频数据frame,在通过swr重采样生成新的audioframe ,最后用SDL_QueueAudio填充,直接缓存进播放设备。

备注:

懒得去扒重复代码,要复用的同学可以先参考下其他的实现方案,修改初始化和读取部分的代码

总结:

国内能找到的SDL文档,主要是采用的第一套方法,单路流时使用正常,无非就是手动实现回调时繁琐了些。但是多路流的播放相关的资料很少,我也是看到一些线索后在谷歌上找到了相关的开发资料。实验发现可行,实现上简单了很多,多路音频播放正常。

记录下,也为在学习SDL 的同学多留点可用资料

参考地址:SDL2:第五个程序:播放pcm数据_SuperLi-CSDN博客_sdl_queueaudio

FFMEPEG+SDL2 多路音频采集播放相关推荐

  1. WebRTC系列-音频ADM播放采集的停止与开始

    文章目录 1. 媒体控制相关接口 1.1 初始化及停止 1.2 其他接口 2. 录制开始/暂停接口 3. 播放开始/暂停接口 上一篇 WebRTC系列 – iOS 音频采集播放之 ADM.APM和Au ...

  2. 【小程序】PCM音频录制播放小工具

    VS2010工程源码下载链接: https://pan.baidu.com/s/1Vf6FOISDXDjORyLcQqCErw PCM是windows系统录音后得到的纯音频数据,需要添加头部说明信息才 ...

  3. Linux下Qt使用QAudio相关类进行音频采集,使用Windows下的Matlab软件播放

    Qt集成的QAudio相关类可以很方便的进行音频采集,主要涉及到以下几个类: #include <QAudioInput> #include <QAudioDeviceInfo> ...

  4. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

  5. 实时音频采集与播放技术的研究

    实时音频采集与播放技术的研究 荣治国 陈松乔 (中南大学信息工程学院 湖南 长沙 410083) 介绍了音频采集.播放的三种技术,分别给出实现模型,并对三种技术作出对比分析,以此提出了声音实时传输的依 ...

  6. Windows上的音频采集技术

    在制作发布端的时候,需要采集到声卡的输出信号,以便与麦克风的输入信号进行混音,对于音频处理的技术,主要有如下几种: 采集麦克风输入 采集声卡输出 将音频数据送入声卡进行播放 对多路音频输入进行混音处理 ...

  7. android多音频输出,基于Android车载系统的多路音频输出的方法、装置及系统与流程...

    本发明涉及Android车载系统领域,特别涉及一种基于Android车载系统的多路音频输出的方法.装置及系统. 背景技术: 车载系统主要由主机.显示屏.操作键盘(遥控器)和天线组成.它实现了野外踏勘. ...

  8. Windows上的音频采集技术比对

    [转]Windows上的音频采集技术 转自http://blog.csdn.net/wxl1986622/article/details/44230149 前一段时间接到一个任务,需要采集到声卡的输出 ...

  9. Windows平台音频采集技术介绍

    音频处理的相关技术: 采集麦克风输入 采集声卡输出 将音频数据送入声卡进行播放 对多路音频输入进行混音处理 在Windows操作系统上,音频处理技术主要是采用微软提供的相关API:Wave系列API函 ...

最新文章

  1. 小x的质数(线性O(n)筛素数)
  2. [转] C# Winform 拦截关闭按钮触发的事件
  3. 常考数据结构与算法:表达式求值
  4. imp导入时出现imp-00017 ora-06550的解决办法
  5. python_文件处理
  6. 什么是rip协议其优缺点_RIP协议详解
  7. Linux安装宝塔面板
  8. PVSCSI还是LSI logic?VM SCSI控制器驱动的选择
  9. 百试不爽的求爱技巧百试不爽的求爱技巧
  10. 有知识和没有知识的两种人,哪种人最幸福,他们的区别在哪里?
  11. Drop user 报ORA-00600 [KTSSDRP1]
  12. C#中窗体的数据传递
  13. 数据结构试卷及答案(九)
  14. ShenYu网关数据同步源码分析
  15. SimpleDateFormat格式设置24小时制时
  16. Fuzzing及Sulley简介
  17. 北京进一步强化节能实施,能耗监测、余热回收等为数据中心重点
  18. 本科毕设课题之OJ开发(1)--评测机
  19. pc控制iphone的软件_评论:苹果M1芯片版MacBook和Mac Mini将颠覆整个PC行业?
  20. Nacos学习日记6-基于Springboot框架的Nacos服务注册报错:Application run failed

热门文章

  1. cache、内存、虚拟内存
  2. 专访北邮教授孙松林:5G尚处第一阶段 中国定会独领风骚
  3. proj Java_proj 一个炫酷的飞机大战java游戏,很好玩的,很酷炫 用了 的图形界面 Games 256万源代码下载- www.pudn.com...
  4. 【mud】object增加颜色的代码以及参考
  5. 美团点评、小米、菜鸟等等目前遇到的面试题(更新中)
  6. Oracle的视图和索引
  7. Unity使用SteamVR2.0 SteamVRInput配置和使用
  8. 【SpringBoot】“@Async” 实现异步执行任务
  9. 性能测试总结(performance testing)(一)
  10. 大家好,我就是区块链本人。今天,我要给你们介绍我的家族……