当前位置:   article > 正文

开源ffmpeg(三)——音频拉流、解码以及重采样_ffmpeg怎么实现拉流

ffmpeg怎么实现拉流

对于ffmpeg介绍和如何输出ffmpeg日志可以参照之前的博客。

该篇博客是用于学习如何使用ffmpeg进行读取音频(包括本地和远端),并对读取流进行音频解码、以及进行重采样的操作。如果现在看官对于音频解码不是很熟悉,建议可以多看看雷神的文章,膜拜+缅怀雷神。

这里有对于音频解码基础的介绍:

视音频编解码技术零基础学习方法

PCM音频数据格式介绍

一、API介绍

1.流程介绍

下文会把一些主要的API做个介绍,我们可以简单理解使用这些API的主要目的就是为了获取:流上下文、解码器、重采样工具。

流上下文:用于读包

解码器:将读取的包进行解码为pcm

重采样工具:将pcm数据重采样为需要的格式

接下来就是拉流、解码、重采样的一个基础流程:

其中每个框都代表一个线程,当然,播放并未体现在本文中。而且值得注意的是:读包后塞入编码器,和获取解码数据可以放在不同的线程中,特别是在做视频工作的时候。因为在做解码工作和重采样时,是一件十分耗时的事情,我们不应该让解码阻塞住读包的过程。 相对于视频解码,音频解码是一个快速的事情。

2.主要API介绍

3.备注

由于笔者这边使用的是ffmpeg 4.4,与老版本是有一些差别的,例如:

一些API是已经废弃的,例如以下API是为了初始化ffmpeg的库:

av_register_all

avformat_network_init

又有一些API是被替换了的,例如以下API是为了对于寻解码器、释放包等:

avcodec_decode_audio4

av_free_packet

avcodec_alloc_context3

虽然已经废弃,但是还是接口还是保留下来的,只要在使用的时候小心一点即可

二、代码实例

1.头文件

  1. #pragma once
  2. extern "C" {
  3. #include "include/libavformat/avformat.h"
  4. #include "include/libavcodec/avcodec.h"
  5. #include "include/libavutil/avutil.h"
  6. #include "include/libswresample/swresample.h"
  7. }
  8. #include <iostream>
  9. #include <mutex>
  10. #include <Windows.h>
  11. namespace AudioReadFrame
  12. {
  13. #define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 16bit audio 2 channel
  14. //自定义结构体存储信息
  15. struct FileAudioInst
  16. {
  17. long long duration; ///< second
  18. long long curl_time; ///< second
  19. int sample_rate; ///< samples per second
  20. int channels; ///< number of audio channels
  21. FileAudioInst()
  22. {
  23. duration = 0;
  24. curl_time = 0;
  25. sample_rate = 0;
  26. channels = 0;
  27. }
  28. };
  29. //拉流线程状态
  30. enum ThreadState
  31. {
  32. run = 1,
  33. exit,
  34. };
  35. class CAudioReadFrame
  36. {
  37. public:
  38. CAudioReadFrame();
  39. ~CAudioReadFrame();
  40. public:
  41. //加载流文件
  42. bool LoadAudioFile(const char* pAudioFilePath);
  43. //开始读流
  44. bool StartReadFile();
  45. //停止读流
  46. bool StopReadFile();
  47. private:
  48. //释放资源
  49. bool FreeResources();
  50. //改变拉流线程的装填
  51. void ChangeThreadState(ThreadState eThreadState);
  52. //拉流线程
  53. void ReadFrameThreadProc();
  54. //utf转GBK
  55. std::string UTF8ToGBK(const std::string& strUTF8);
  56. private:
  57. typedef std::unique_ptr<std::thread> ThreadPtr;
  58. //目的是为了重定向输出ffmpeg日志到本地文件
  59. #define PRINT_LOG 0
  60. #ifdef PRINT_LOG
  61. private:
  62. static FILE* m_pLogFile;
  63. static void LogCallback(void* ptr, int level, const char* fmt, va_list vl);
  64. #endif
  65. //目的是为了将拉流数据dump下来
  66. #define DUMP_AUDIO 1
  67. #ifdef DUMP_AUDIO
  68. FILE* decode_file;
  69. #endif // DUMP_FILE
  70. private:
  71. bool m_bIsReadyForRead;
  72. int m_nStreamIndex;
  73. uint8_t* m_pSwrBuffer;
  74. std::mutex m_lockResources;
  75. std::mutex m_lockThread;
  76. FileAudioInst* m_pFileAudioInst;
  77. ThreadPtr m_pReadFrameThread;
  78. ThreadState m_eThreadState;
  79. private:
  80. SwrContext* m_pSwrContext; //重采样
  81. AVFrame* m_pAVFrame; //音频包
  82. AVCodec* m_pAVCodec; //编解码器
  83. AVPacket* m_pAVPack; //读包
  84. AVCodecParameters * m_pAVCodecParameters; //编码参数
  85. AVCodecContext* m_pAVCodecContext; //解码上下文
  86. AVFormatContext* m_pAVFormatContext; //IO上下文
  87. };
  88. }

2.源文件

  1. #include "CAudioReadFrame.h"
  2. #include <sstream>
  3. namespace AudioReadFrame
  4. {
  5. #ifdef FFMPEG_LOG_OUT
  6. FILE* CAudioReadFrame::m_pLogFile = nullptr;
  7. void CAudioReadFrame::LogCallback(void* ptr, int level, const char* fmt, va_list vl)
  8. {
  9. if (m_pLogFile == nullptr)
  10. {
  11. m_pLogFile = fopen("E:\\log\\log.txt", "w+");
  12. }
  13. if (m_pLogFile)
  14. {
  15. vfprintf(m_pLogFile, fmt, vl);
  16. fflush(m_pLogFile);
  17. }
  18. }
  19. #endif
  20. CAudioReadFrame::CAudioReadFrame()
  21. {
  22. std::cout << av_version_info() << std::endl;
  23. #if DUMP_AUDIO
  24. decode_file = fopen("E:\\log\\decode_file.pcm", "wb+");
  25. #endif
  26. }
  27. CAudioReadFrame::~CAudioReadFrame()
  28. {
  29. #if DUMP_AUDIO
  30. if (decode_file) {
  31. fclose(decode_file);
  32. decode_file = nullptr;
  33. }
  34. #endif
  35. StopReadFile();
  36. }
  37. bool CAudioReadFrame::LoadAudioFile(const char* pAudioFilePath)
  38. {
  39. #ifdef FFMPEG_LOG_OUT
  40. if (m_pLogFile != nullptr)
  41. {
  42. fclose(m_pLogFile);
  43. m_pLogFile = nullptr;
  44. }
  45. time_t t = time(nullptr);
  46. struct tm* now = localtime(&t);
  47. std::stringstream time;
  48. time << now->tm_year + 1900 << "/";
  49. time << now->tm_mon + 1 << "/";
  50. time << now->tm_mday << "/";
  51. time << now->tm_hour << ":";
  52. time << now->tm_min << ":";
  53. time << now->tm_sec << std::endl;
  54. std::cout << time.str();
  55. av_log_set_level(AV_LOG_TRACE); //设置日志级别
  56. av_log_set_callback(LogCallback);
  57. av_log(NULL, AV_LOG_INFO, time.str().c_str());
  58. #endif
  59. ChangeThreadState(ThreadState::exit);
  60. FreeResources();
  61. av_log_set_level(AV_LOG_TRACE); //设置日志级别
  62. av_log(NULL, AV_LOG_DEBUG, "the debug line:%d, string:%s", __LINE__, "hello");
  63. m_nStreamIndex = -1;
  64. m_pAVFormatContext = avformat_alloc_context();
  65. m_pAVFrame = av_frame_alloc();
  66. m_pSwrContext = swr_alloc();
  67. m_pFileAudioInst = new FileAudioInst;
  68. m_pSwrBuffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE);
  69. m_pAVPack = av_packet_alloc();
  70. //Open an input stream and read the header
  71. if (avformat_open_input(&m_pAVFormatContext, pAudioFilePath, NULL, NULL) != 0) {
  72. av_log(NULL, AV_LOG_ERROR, "Couldn't open input stream.\n");
  73. return false;
  74. }
  75. //Read packets of a media file to get stream information
  76. if (avformat_find_stream_info(m_pAVFormatContext, NULL) < 0) {
  77. av_log(NULL, AV_LOG_ERROR, "Couldn't find stream information.\n");
  78. return false;
  79. }
  80. for (unsigned int i = 0; i < m_pAVFormatContext->nb_streams; i++)
  81. {
  82. //因为一个url可以包含多股,如果存在多股流,找到音频流,因为现在只读MP3,所以只找音频流
  83. if (m_pAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
  84. m_nStreamIndex = i;
  85. break;
  86. }
  87. }
  88. if (m_nStreamIndex == -1) {
  89. av_log(NULL, AV_LOG_ERROR, "Didn't find a audio stream.\n");
  90. return false;
  91. }
  92. m_pAVCodecParameters = m_pAVFormatContext->streams[m_nStreamIndex]->codecpar;
  93. m_pAVCodec = (AVCodec *)avcodec_find_decoder(m_pAVCodecParameters->codec_id);
  94. // Open codec
  95. m_pAVCodecContext = avcodec_alloc_context3(m_pAVCodec);
  96. avcodec_parameters_to_context(m_pAVCodecContext, m_pAVCodecParameters);
  97. if (avcodec_open2(m_pAVCodecContext, m_pAVCodec, NULL) < 0) {
  98. av_log(NULL, AV_LOG_ERROR, "Could not open codec.\n");
  99. return false;
  100. }
  101. //初始化重采样 采样率为双通 short, 48k
  102. AVChannelLayout outChannelLayout;
  103. AVChannelLayout inChannelLayout;
  104. outChannelLayout.nb_channels = 2;
  105. inChannelLayout.nb_channels = m_pAVCodecContext->ch_layout.nb_channels;
  106. if (swr_alloc_set_opts2(&m_pSwrContext, &outChannelLayout, AV_SAMPLE_FMT_S16, 48000,
  107. &inChannelLayout, m_pAVCodecContext->sample_fmt, m_pAVCodecContext->sample_rate, 0, NULL)
  108. != 0)
  109. {
  110. av_log(NULL, AV_LOG_ERROR, "swr_alloc_set_opts2 fail.\n");
  111. return false;
  112. }
  113. swr_init(m_pSwrContext);
  114. //保留流信息
  115. m_pFileAudioInst->duration = m_pAVFormatContext->duration / 1000;//ms
  116. m_pFileAudioInst->channels = m_pAVCodecParameters->ch_layout.nb_channels;
  117. m_pFileAudioInst->sample_rate = m_pAVCodecParameters->sample_rate;
  118. m_bIsReadyForRead = true;
  119. return true;
  120. }
  121. bool CAudioReadFrame::StartReadFile()
  122. {
  123. if (!m_bIsReadyForRead)
  124. {
  125. av_log(NULL, AV_LOG_ERROR, "File not ready");
  126. return false;
  127. }
  128. if (m_pReadFrameThread != nullptr)
  129. {
  130. if (m_pReadFrameThread->joinable())
  131. {
  132. m_pReadFrameThread->join();
  133. m_pReadFrameThread.reset(nullptr);
  134. }
  135. }
  136. ChangeThreadState(ThreadState::run);
  137. m_pReadFrameThread.reset(new std::thread(&CAudioReadFrame::ReadFrameThreadProc, this));
  138. return true;
  139. }
  140. bool CAudioReadFrame::StopReadFile()
  141. {
  142. ChangeThreadState(ThreadState::exit);
  143. if (m_pReadFrameThread != nullptr)
  144. {
  145. if (m_pReadFrameThread->joinable())
  146. {
  147. m_pReadFrameThread->join();
  148. m_pReadFrameThread.reset(nullptr);
  149. }
  150. }
  151. FreeResources();
  152. return true;
  153. }
  154. void CAudioReadFrame::ReadFrameThreadProc()
  155. {
  156. while (true)
  157. {
  158. if (m_eThreadState == ThreadState::exit)
  159. {
  160. break;
  161. }
  162. //读取一个包
  163. int nRet = av_read_frame(m_pAVFormatContext, m_pAVPack);
  164. if (nRet != 0)
  165. {
  166. std::stringstream logInfo;
  167. logInfo << "read frame no data error:" << nRet << std::endl;
  168. av_log(NULL, AV_LOG_ERROR, logInfo.str().c_str());
  169. ChangeThreadState(ThreadState::exit);
  170. continue;
  171. }
  172. //判断读取流是否正确
  173. if (m_pAVPack->stream_index != m_nStreamIndex)
  174. {
  175. std::stringstream logInfo;
  176. logInfo << "read frame no data error:" << std::endl;
  177. av_log(NULL, AV_LOG_ERROR, logInfo.str().c_str());
  178. continue;
  179. }
  180. //将一个包放入解码器
  181. nRet = avcodec_send_packet(m_pAVCodecContext, m_pAVPack);
  182. if (nRet < 0) {
  183. std::stringstream logInfo;
  184. logInfo << "avcodec_send_packet error:" << nRet << std::endl;
  185. av_log(NULL, AV_LOG_ERROR, logInfo.str().c_str());
  186. continue;
  187. }
  188. //从解码器读取解码后的数据
  189. nRet = avcodec_receive_frame(m_pAVCodecContext, m_pAVFrame);
  190. if (nRet != 0) {
  191. std::stringstream logInfo;
  192. logInfo << "avcodec_receive_frame error:" << nRet << std::endl;
  193. av_log(NULL, AV_LOG_ERROR, logInfo.str().c_str());
  194. continue;
  195. }
  196. //重采样,采样率不变
  197. memset(m_pSwrBuffer, 0, MAX_AUDIO_FRAME_SIZE);
  198. nRet = swr_convert(m_pSwrContext, &m_pSwrBuffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)m_pAVFrame->data, m_pAVFrame->nb_samples);
  199. if (nRet <0)
  200. {
  201. std::stringstream logInfo;
  202. logInfo << "swr_convert error:" << nRet << std::endl;
  203. av_log(NULL, AV_LOG_ERROR, logInfo.str().c_str());
  204. continue;
  205. }
  206. #if DUMP_AUDIO
  207. //获取重采样之后的buffer大小
  208. int buffSize = av_samples_get_buffer_size(NULL, 2, nRet, AV_SAMPLE_FMT_S16, 1);
  209. fwrite((char*)m_pSwrBuffer, 1, buffSize, decode_file);
  210. #endif
  211. av_packet_unref(m_pAVPack);
  212. }
  213. }
  214. bool CAudioReadFrame::FreeResources()
  215. {
  216. std::lock_guard<std::mutex> locker(m_lockResources);
  217. if (m_pSwrBuffer)
  218. {
  219. av_free(m_pSwrBuffer);
  220. m_pSwrBuffer = nullptr;
  221. }
  222. if (m_pFileAudioInst)
  223. {
  224. delete m_pFileAudioInst;
  225. m_pFileAudioInst = nullptr;
  226. }
  227. if (m_pSwrContext)
  228. {
  229. swr_free(&m_pSwrContext);
  230. m_pSwrContext = nullptr;
  231. }
  232. if (m_pAVFrame)
  233. {
  234. av_frame_free(&m_pAVFrame);
  235. m_pAVFrame = nullptr;
  236. }
  237. if (m_pAVPack)
  238. {
  239. av_packet_free(&m_pAVPack);
  240. m_pAVPack = nullptr;
  241. }
  242. if (m_pAVFormatContext)
  243. {
  244. avformat_free_context(m_pAVFormatContext);
  245. m_pAVFormatContext = nullptr;
  246. }
  247. if (m_pAVCodecParameters)
  248. {
  249. avcodec_parameters_free(&m_pAVCodecParameters);
  250. m_pAVCodecParameters = nullptr;
  251. }
  252. if (m_pAVCodecContext)
  253. {
  254. avcodec_close(m_pAVCodecContext);
  255. m_pAVCodecContext = nullptr;
  256. }
  257. m_bIsReadyForRead = false;
  258. return true;
  259. }
  260. void CAudioReadFrame::ChangeThreadState(ThreadState eThreadState)
  261. {
  262. std::lock_guard<std::mutex> locker(m_lockThread);
  263. if (m_eThreadState != eThreadState)
  264. {
  265. m_eThreadState = eThreadState;
  266. }
  267. }
  268. std::string CAudioReadFrame::UTF8ToGBK(const std::string& strUTF8)
  269. {
  270. int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, NULL, 0);
  271. wchar_t* wszGBK = new wchar_t[len + 1];
  272. memset(wszGBK, 0, len * 2 + 2);
  273. MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, wszGBK, len);
  274. len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
  275. char *szGBK = new char[len + 1];
  276. memset(szGBK, 0, len + 1);
  277. WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
  278. //strUTF8 = szGBK;
  279. std::string strTemp(szGBK);
  280. delete[]szGBK;
  281. delete[]wszGBK;
  282. return strTemp;
  283. }
  284. }

3.使用

  1. #include "CAudioReadFrame.h"
  2. int main()
  3. {
  4. AudioReadFrame::CAudioReadFrame cTest;
  5. cTest.LoadAudioFile("E:\\原音_女声.mp3");
  6. cTest.StartReadFile();
  7. system("pause");
  8. return 0;
  9. }

现在看一下音谱:

总结

以上就是对于音频的拉流、解码以及重采样的流程了,该例子中,拉取的是本地流,不过如果给一个远端直播流是同样可以成功的。

当然这些只是入门的操作,在实际使用,在拉起直播流、本地流、远端文件流的处理方案都应该是不同的,毕竟场景不同,方案也不同。至于为什么使用不同的方案,会在接下来的文章中再做解释。

原文链接:开源ffmpeg(三)--音频拉流、解码以及重采样_ffmpeg音频推流_山河君的博客-CSDN博客

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/446372
推荐阅读
相关标签
  

闽ICP备14008679号