|
@@ -0,0 +1,466 @@
|
|
|
+/**
|
|
|
+ * 叶海辉
|
|
|
+ * QQ群121376426
|
|
|
+ * http://blog.yundiantech.com/
|
|
|
+ */
|
|
|
+
|
|
|
+#include "AppConfig.h"
|
|
|
+#include "GetAudioThread.h"
|
|
|
+
|
|
|
+#include <thread>
|
|
|
+
|
|
|
+#if defined __linux
|
|
|
+#include <xcb/xcb.h>
|
|
|
+#endif
|
|
|
+
|
|
|
+//Show Dshow Device
|
|
|
+void show_dshow_device()
|
|
|
+{
|
|
|
+ AVFormatContext *pFormatCtx = avformat_alloc_context();
|
|
|
+ AVDictionary* options = nullptr;
|
|
|
+ av_dict_set(&options,"list_devices","true",0);
|
|
|
+ AVInputFormat *iformat = av_find_input_format("dshow");
|
|
|
+ printf("========Device Info=============\n");
|
|
|
+ avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);
|
|
|
+ printf("================================\n");
|
|
|
+}
|
|
|
+
|
|
|
+GetAudioThread::GetAudioThread()
|
|
|
+{
|
|
|
+ m_isRun = false;
|
|
|
+
|
|
|
+ pFormatCtx = nullptr;
|
|
|
+ aCodecCtx = nullptr;
|
|
|
+ aFrame = nullptr;
|
|
|
+
|
|
|
+ m_pause = false;
|
|
|
+
|
|
|
+ aFrame = nullptr;
|
|
|
+ aFrame_ReSample = nullptr;
|
|
|
+
|
|
|
+ mCallBackFunc = nullptr;
|
|
|
+ mFuncIndex = -1;
|
|
|
+
|
|
|
+ audio_buf_size_L = 0;
|
|
|
+ audio_buf_size_R = 0;
|
|
|
+
|
|
|
+ mONEFrameSize = 0;
|
|
|
+}
|
|
|
+
|
|
|
+GetAudioThread::~GetAudioThread()
|
|
|
+{
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+bool GetAudioThread::init(const char * const deviceName)
|
|
|
+{
|
|
|
+// show_dshow_device();
|
|
|
+
|
|
|
+ AVCodec *pCodec = nullptr;
|
|
|
+
|
|
|
+ pFormatCtx = avformat_alloc_context();
|
|
|
+
|
|
|
+#if defined(WIN32)
|
|
|
+
|
|
|
+ AVInputFormat *ifmt = av_find_input_format("dshow"); //使用dshow
|
|
|
+
|
|
|
+ char deviceNameStr[512] = {0};
|
|
|
+ sprintf(deviceNameStr, "audio=%s", deviceName);
|
|
|
+
|
|
|
+// if(avformat_open_input(&pFormatCtx, "audio=virtual-audio-capturer", ifmt, nullptr)!=0)
|
|
|
+ if(avformat_open_input(&pFormatCtx, deviceNameStr, ifmt, nullptr)!=0)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "Couldn't open input stream video.(无法打开输入流)\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+#elif defined __linux
|
|
|
+//Linux
|
|
|
+ //Linux
|
|
|
+ AVInputFormat *ifmt = av_find_input_format("alsa");
|
|
|
+ if(avformat_open_input(&pFormatCtx, deviceName, ifmt, NULL)!=0)
|
|
|
+ {
|
|
|
+ printf("Couldn't open input stream. default\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+#else
|
|
|
+ show_avfoundation_device();
|
|
|
+ //Mac
|
|
|
+ AVInputFormat *ifmt=av_find_input_format("avfoundation");
|
|
|
+ //Avfoundation
|
|
|
+ //[video]:[audio]
|
|
|
+ if(avformat_open_input(&pFormatCtx,"0",ifmt,nullptr)!=0)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "Couldn't open input stream.\n");
|
|
|
+ return VideoOpenFailed;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ audioStream = -1;
|
|
|
+ aCodecCtx = nullptr;
|
|
|
+
|
|
|
+ for(unsigned int i=0; i < pFormatCtx->nb_streams; i++)
|
|
|
+ {
|
|
|
+ if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
|
|
|
+ {
|
|
|
+ audioStream = static_cast<int>(i);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(audioStream == -1)
|
|
|
+ {
|
|
|
+ printf("Didn't find a audio stream.(没有找到音频流)\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ //find the decoder
|
|
|
+ aCodecCtx = avcodec_alloc_context3(nullptr);
|
|
|
+ avcodec_parameters_to_context(aCodecCtx, pFormatCtx->streams[audioStream]->codecpar);
|
|
|
+
|
|
|
+ pCodec = avcodec_find_decoder(aCodecCtx->codec_id);
|
|
|
+
|
|
|
+ if(pCodec == nullptr)
|
|
|
+ {
|
|
|
+ printf("audio Codec not found.\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(avcodec_open2(aCodecCtx, pCodec, nullptr)<0)
|
|
|
+ {
|
|
|
+ printf("Could not open audio codec.\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ///解码音频相关
|
|
|
+ aFrame = av_frame_alloc();
|
|
|
+
|
|
|
+ initResample();
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::deInit()
|
|
|
+{
|
|
|
+// if (outBufferYuv)
|
|
|
+// {
|
|
|
+// av_free(outBufferYuv);
|
|
|
+// outBufferYuv = nullptr;
|
|
|
+// }
|
|
|
+
|
|
|
+// if (pFrame)
|
|
|
+// {
|
|
|
+// av_free(pFrame);
|
|
|
+// pFrame = nullptr;
|
|
|
+// }
|
|
|
+
|
|
|
+// if (pFrameYUV)
|
|
|
+// {
|
|
|
+// av_free(pFrameYUV);
|
|
|
+// pFrameYUV = nullptr;
|
|
|
+// }
|
|
|
+
|
|
|
+// if (pCodecCtx)
|
|
|
+// avcodec_close(pCodecCtx);
|
|
|
+
|
|
|
+// avformat_close_input(&pFormatCtx);
|
|
|
+// avformat_free_context(pFormatCtx);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::startRecord(int outOneFrameSize, std::function<void (PCMFramePtr pcmFrame, int index)> func, int funcIndex)
|
|
|
+{
|
|
|
+ m_isRun = true;
|
|
|
+
|
|
|
+ mONEFrameSize = outOneFrameSize;
|
|
|
+
|
|
|
+ mCallBackFunc = func;
|
|
|
+ mFuncIndex = funcIndex;
|
|
|
+
|
|
|
+ //启动新的线程
|
|
|
+ std::thread([&](GetAudioThread *pointer)
|
|
|
+ {
|
|
|
+ pointer->run();
|
|
|
+
|
|
|
+ }, this).detach();
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::pauseRecord()
|
|
|
+{
|
|
|
+ m_pause = true;
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::restoreRecord()
|
|
|
+{
|
|
|
+ m_getFirst = false;
|
|
|
+ m_pause = false;
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::stopRecord()
|
|
|
+{
|
|
|
+ m_isRun = false;
|
|
|
+}
|
|
|
+
|
|
|
+bool GetAudioThread::initResample()
|
|
|
+{
|
|
|
+ //重采样设置选项-----------------------------------------------------------start
|
|
|
+ aFrame_ReSample = nullptr;
|
|
|
+
|
|
|
+ //frame->16bit 44100 PCM 统一音频采样格式与采样率
|
|
|
+ swrCtx = nullptr;
|
|
|
+
|
|
|
+ //输入的声道布局
|
|
|
+ int in_ch_layout;
|
|
|
+
|
|
|
+ /// 由于ffmpeg编码aac需要输入FLTP格式的数据。
|
|
|
+ /// 因此这里将音频重采样成44100 双声道 AV_SAMPLE_FMT_FLTP
|
|
|
+ //重采样设置选项----------------
|
|
|
+ //输入的采样格式
|
|
|
+ in_sample_fmt = aCodecCtx->sample_fmt;
|
|
|
+ //输出的采样格式 32bit PCM
|
|
|
+ out_sample_fmt = AV_SAMPLE_FMT_FLTP;
|
|
|
+ //输入的采样率
|
|
|
+ in_sample_rate = aCodecCtx->sample_rate;
|
|
|
+ //输入的声道布局
|
|
|
+ in_ch_layout = aCodecCtx->channel_layout;
|
|
|
+
|
|
|
+ //输出的采样率
|
|
|
+ out_sample_rate = 44100;
|
|
|
+
|
|
|
+ //输出的声道布局
|
|
|
+ out_ch_layout = AV_CH_LAYOUT_STEREO;
|
|
|
+ audio_tgt_channels = av_get_channel_layout_nb_channels(out_ch_layout);
|
|
|
+
|
|
|
+// //输出的声道布局
|
|
|
+// out_ch_layout = av_get_default_channel_layout(audio_tgt_channels); ///AV_CH_LAYOUT_STEREO
|
|
|
+// out_ch_layout &= ~AV_CH_LAYOUT_STEREO;
|
|
|
+
|
|
|
+ if (in_ch_layout <= 0)
|
|
|
+ {
|
|
|
+ if (aCodecCtx->channels == 2)
|
|
|
+ {
|
|
|
+ in_ch_layout = AV_CH_LAYOUT_STEREO;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ in_ch_layout = AV_CH_LAYOUT_MONO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ swrCtx = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt, out_sample_rate,
|
|
|
+ in_ch_layout, in_sample_fmt, in_sample_rate, 0, nullptr);
|
|
|
+
|
|
|
+ /** Open the resampler with the specified parameters. */
|
|
|
+ int ret = swr_init(swrCtx);
|
|
|
+ if (ret < 0)
|
|
|
+ {
|
|
|
+ char buff[128]={0};
|
|
|
+ av_strerror(ret, buff, 128);
|
|
|
+
|
|
|
+ fprintf(stderr, "Could not open resample context %s\n", buff);
|
|
|
+ swr_free(&swrCtx);
|
|
|
+ swrCtx = nullptr;
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::run()
|
|
|
+{
|
|
|
+ struct SwsContext *img_convert_ctx = nullptr;
|
|
|
+
|
|
|
+ AVPacket packet;
|
|
|
+
|
|
|
+ int64_t firstTime = AppConfig::getTimeStamp_MilliSecond();
|
|
|
+ m_getFirst = false;
|
|
|
+ int64_t timeIndex = 0;
|
|
|
+
|
|
|
+ while(m_isRun)
|
|
|
+ {
|
|
|
+ if (av_read_frame(pFormatCtx, &packet)<0)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "read failed! \n");
|
|
|
+ AppConfig::mSleep(10);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_pause)
|
|
|
+ {
|
|
|
+ av_packet_unref(&packet);
|
|
|
+ AppConfig::mSleep(10);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(packet.stream_index == audioStream)
|
|
|
+ {
|
|
|
+ int64_t time = 0;
|
|
|
+// if (m_saveVideoFileThread)
|
|
|
+ {
|
|
|
+ if (m_getFirst)
|
|
|
+ {
|
|
|
+ int64_t secondTime = AppConfig::getTimeStamp_MilliSecond();
|
|
|
+ time = secondTime - firstTime + timeIndex;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ firstTime = AppConfig::getTimeStamp_MilliSecond();
|
|
|
+ timeIndex = 0;
|
|
|
+ m_getFirst = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (int ret = avcodec_send_packet(aCodecCtx, &packet) && ret != 0)
|
|
|
+ {
|
|
|
+ char buffer[1024] = {0};
|
|
|
+ av_strerror(ret, buffer, 1024);
|
|
|
+ fprintf(stderr, "input AVPacket to decoder failed! ret = %d %s\n", ret, buffer);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // while (0 == avcodec_receive_frame(pCodecCtx, pFrame))
|
|
|
+ while(1)
|
|
|
+ {
|
|
|
+ int ret = avcodec_receive_frame(aCodecCtx, aFrame);
|
|
|
+ if (ret != 0)
|
|
|
+ {
|
|
|
+ // char buffer[1024] = {0};
|
|
|
+ // av_strerror(ret, buffer, 1024);
|
|
|
+ // fprintf(stderr, "avcodec_receive_frame = %d %s\n", ret, buffer);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ///解码一帧后才能获取到采样率等信息,因此将初始化放到这里
|
|
|
+ if (aFrame_ReSample == nullptr)
|
|
|
+ {
|
|
|
+ aFrame_ReSample = av_frame_alloc();
|
|
|
+
|
|
|
+ int nb_samples = av_rescale_rnd(swr_get_delay(swrCtx, out_sample_rate) + aFrame->nb_samples, out_sample_rate, in_sample_rate, AV_ROUND_UP);
|
|
|
+
|
|
|
+ // av_samples_fill_arrays(aFrame_ReSample->data, aFrame_ReSample->linesize, audio_buf_resample, audio_tgt_channels, aFrame_ReSample->nb_samples, out_sample_fmt, 0);
|
|
|
+
|
|
|
+ aFrame_ReSample->format = out_sample_fmt;
|
|
|
+ aFrame_ReSample->channel_layout = out_ch_layout;
|
|
|
+ aFrame_ReSample->sample_rate = out_sample_rate;
|
|
|
+ aFrame_ReSample->nb_samples = nb_samples;
|
|
|
+
|
|
|
+ ret = av_frame_get_buffer(aFrame_ReSample, 0);
|
|
|
+ if (ret < 0)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "Error allocating an audio buffer\n");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ///执行重采样
|
|
|
+ int len2 = swr_convert(swrCtx, aFrame_ReSample->data, aFrame_ReSample->nb_samples, (const uint8_t**)aFrame->data, aFrame->nb_samples);
|
|
|
+
|
|
|
+ ///下面这两种方法计算的大小是一样的
|
|
|
+ #if 0
|
|
|
+ int resampled_data_size = len2 * audio_tgt_channels * av_get_bytes_per_sample(out_sample_fmt);
|
|
|
+ #else
|
|
|
+ int resampled_data_size = av_samples_get_buffer_size(NULL, audio_tgt_channels, aFrame_ReSample->nb_samples, out_sample_fmt, 1);
|
|
|
+ #endif
|
|
|
+
|
|
|
+ int OneChannelDataSize = resampled_data_size / audio_tgt_channels;
|
|
|
+
|
|
|
+//fprintf(stderr, "OneChannelDataSize=%d %d %d\n", OneChannelDataSize, mAudioEncoder->getONEFrameSize(), aFrame->nb_samples);
|
|
|
+/// 由于平面模式的pcm存储方式为:LLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRRR,
|
|
|
+/// 因此这里需要将左右声道数据分开存入文件才可以正常播放。
|
|
|
+/// 使用播放器单独播放左右 声道数据测试即可(以单声道 44100 32bit打开播放)。
|
|
|
+//static FILE *fp1 = fopen("out-L.pcm", "wb");
|
|
|
+//fwrite(aFrame_ReSample->data[0], 1, OneChannelDataSize, fp1);
|
|
|
+//if (audio_tgt_channels >= 2)
|
|
|
+//{
|
|
|
+// static FILE *fp2 = fopen("out-R.pcm", "wb");
|
|
|
+// fwrite(aFrame_ReSample->data[1], 1, OneChannelDataSize, fp2);
|
|
|
+//}
|
|
|
+ dealWithAudioFrame(OneChannelDataSize);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ fprintf(stderr, "other %d \n", packet.stream_index);
|
|
|
+ }
|
|
|
+
|
|
|
+ av_packet_unref(&packet);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ sws_freeContext(img_convert_ctx);
|
|
|
+
|
|
|
+ fprintf(stderr, "record stopping... \n");
|
|
|
+
|
|
|
+ m_pause = false;
|
|
|
+
|
|
|
+ deInit();
|
|
|
+
|
|
|
+ fprintf(stderr, "record finished! \n");
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void GetAudioThread::dealWithAudioFrame(const int &OneChannelDataSize)
|
|
|
+{
|
|
|
+ ///编码器一帧的采样为1024,而这里一次获取到的不是1024,因此需要放入队列,然后每次从队里取1024次采样交给编码器。
|
|
|
+ ///PS:平面模式的pcm存储方式为:LLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRRR,需要了解这句话的含义。
|
|
|
+
|
|
|
+ memcpy(audio_buf_L + audio_buf_size_L, aFrame_ReSample->data[0], OneChannelDataSize);
|
|
|
+ audio_buf_size_L += OneChannelDataSize;
|
|
|
+
|
|
|
+ if (audio_tgt_channels >= 2)
|
|
|
+ {
|
|
|
+ memcpy(audio_buf_R + audio_buf_size_R, aFrame_ReSample->data[1], OneChannelDataSize);
|
|
|
+ audio_buf_size_R += OneChannelDataSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ int index = 0;
|
|
|
+
|
|
|
+ int leftSize = audio_buf_size_L;
|
|
|
+
|
|
|
+ int ONEChannelAudioSize = mONEFrameSize / audio_tgt_channels;
|
|
|
+
|
|
|
+ ///由于采集到的数据很大,而编码器一次只需要很少的数据。
|
|
|
+ ///因此将采集到的数据分成多次传给编码器。
|
|
|
+ /// 由于平面模式的pcm存储方式为:LLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRRR,因此这里合并完传给编码器就行了
|
|
|
+ while(1)
|
|
|
+ {
|
|
|
+ if (leftSize >= ONEChannelAudioSize)
|
|
|
+ {
|
|
|
+ uint8_t * buffer = (uint8_t *)malloc(ONEChannelAudioSize * audio_tgt_channels);
|
|
|
+ memcpy(buffer, audio_buf_L+index, ONEChannelAudioSize);
|
|
|
+
|
|
|
+ if (audio_tgt_channels >= 2)
|
|
|
+ {
|
|
|
+ memcpy(buffer+ONEChannelAudioSize, audio_buf_R+index, ONEChannelAudioSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ PCMFramePtr framePtr = std::make_shared<PCMFrame>();
|
|
|
+ framePtr->setFrameBuffer(buffer, ONEChannelAudioSize * audio_tgt_channels);
|
|
|
+
|
|
|
+ free(buffer);
|
|
|
+
|
|
|
+ if (mCallBackFunc != nullptr)
|
|
|
+ {
|
|
|
+ mCallBackFunc(framePtr, mFuncIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ index += ONEChannelAudioSize;
|
|
|
+ leftSize -= ONEChannelAudioSize;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (leftSize > 0)
|
|
|
+ {
|
|
|
+ memcpy(audio_buf_L, audio_buf_L+index, leftSize);
|
|
|
+ memcpy(audio_buf_R, audio_buf_R+index, leftSize);
|
|
|
+ }
|
|
|
+ audio_buf_size_L = leftSize;
|
|
|
+ audio_buf_size_R = leftSize;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|