音视频开发系列(10):基于qt的音频推流
今天分享一下利用qt錄制音頻,然后再利用ffmpeg推流到nginx服務器,最后再利用vlc進行拉流的demo。
首先介紹一下如何利用qt來進行音頻的錄制,qt的音頻錄制主要利用qt的QAudioFormat先進行音頻信息的配置。主要需要配置以下的信息:
QAudioFormat fmt;fmt.setSampleRate(sampleRate);// 采樣率, 一秒采集音頻樣本數量,常設置為44100fmt.setChannelCount(channels); // 音頻通道數fmt.setSampleSize(16); //一個音頻數據大小fmt.setCodec("audio/pcm"); //編碼方式,大多聲卡只支持pcm,也可以通過獲取參數得到聲卡支持參數fmt.setByteOrder(QAudioFormat::LittleEndian); // 小端 存儲還是大端存儲fmt.setSampleType(QAudioFormat::UnSignedInt); // 數據類型,對應的是16位然后使用QAudioDeviceInfo來獲取是否支持改設置信息,如果不支持的話就取其最近的配置。
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();if (!info.isFormatSupported(fmt)){cout << "Audio format not support!" << endl;fmt = info.nearestFormat(fmt);}然后再利用QIODevice開始錄制音頻,具體的讀取方式如下所示。
//一次讀取一幀音頻 由于這種方式讀取不準確,所以僅用來進行判斷if (input->bytesReady() < readSize){QThread::msleep(1);continue;}int size = 0;while (size != readSize){int len = io->read(buf + size, readSize - size);if (len < 0)break;size += len;}if (size != readSize)continue;到這里利用qt進行音頻的錄制就完成了,接下來是利用ffmpeg進行推流,推流的調用的函數和之前視頻推流調用的api一致,只是一些參數的配置進行了改變。
由于推流時音頻的格式是AV_SAMPLE_FMT_FLTP,與qt采集到的格式AV_SAMPLE_FMT_S16不一致,所以需要對采集到的音頻進行重采樣。
這邊利用的是ffmpeg的api函數來進行重采樣,首先需要初始化重采樣的上下文,主要利用
swr_alloc_set_opts()函數,設置參數如下:
SwrContext *asc = NULL;asc = swr_alloc_set_opts(asc,av_get_default_channel_layout(channels), AV_SAMPLE_FMT_FLTP, sampleRate, //輸出格式av_get_default_channel_layout(channels), AV_SAMPLE_FMT_S16, sampleRate,//輸入格式 0,0);if (!asc){ cout << "swr_alloc_set_opts failed!"<<endl;getchar();return -1;}主要是配置輸出和輸入的參數,接著利用swr_init()初始化該上下文。
然后分配好音頻重采樣的輸出空間,配置信息如下:
AVFrame *pcm = av_frame_alloc();pcm->format = outSampleFmt;pcm->channels = channels;pcm->channel_layout = av_get_default_channel_layout(channels);pcm->nb_samples = 1024;//一幀音頻一通道的采樣數量ret = av_frame_get_buffer(pcm, 0); //給pcm分配存儲空間if (ret != 0){char err[1024] = { 0 };av_strerror(ret, err, sizeof(err) - 1);cout << err << endl;getchar();return -1;}然后利用swr_convert()函數進行轉化將轉化后的數據放入pcm中。
接下來需要對音頻的pts進行運算,主要運算如下:
//pts運算//nb_sample/sample_rate =一幀音頻的秒數 //time_base pts=sec * timebase.den pcm->pts = apts;apts += av_rescale_q(pcm->nb_samples, {1,sampleRate},ac->time_base);接下來就是對重采樣后的音頻進行推流,這邊與之前視頻推流的一致,具體的參數配置可以參考我下面分享的代碼:
#include <QtCore/QCoreApplication> #include <QAudioInput> #include <QThread> #include <iostream> extern "C" { #include "libswresample/swresample.h" #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } #pragma comment(lib,"avformat.lib") #pragma comment(lib,"swresample.lib") #pragma comment(lib,"avutil.lib") #pragma comment(lib, "avcodec.lib") using namespace std; int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//注冊所有的編解碼器avcodec_register_all();//注冊所有的封裝器av_register_all();//注冊所有的網絡協議avformat_network_init();char *outUrl = "rtmp://192.168.198.128/live";int sampleRate = 44100;int channels = 2;int sampleByte = 2;AVSampleFormat inSampleFmt = AV_SAMPLE_FMT_S16;AVSampleFormat outSampleFmt = AV_SAMPLE_FMT_FLTP;///1 qt音頻開始錄制 QAudioFormat fmt;//采樣頻率fmt.setSampleRate(sampleRate);//通道數量fmt.setChannelCount(channels);//樣本大小fmt.setSampleSize(sampleByte *8);//格式fmt.setCodec("audio/pcm");//字節序fmt.setByteOrder(QAudioFormat::LittleEndian);fmt.setSampleType(QAudioFormat::UnSignedInt);QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();if (!info.isFormatSupported(fmt)){cout << "Audio format not support!" << endl;fmt = info.nearestFormat(fmt);}cout << "Audio format success" << endl;QAudioInput *input = new QAudioInput(fmt);//開始錄制音頻QIODevice *io= input->start();///2 音頻重采樣SwrContext *asc = NULL;asc = swr_alloc_set_opts(asc,av_get_default_channel_layout(channels), AV_SAMPLE_FMT_FLTP, sampleRate, //輸出格式av_get_default_channel_layout(channels), AV_SAMPLE_FMT_S16, sampleRate,//輸入格式 0,0);if (!asc){ cout << "swr_alloc_set_opts failed!"<<endl;getchar();return -1;}int ret = swr_init(asc);if (ret != 0){char err[1024] = { 0 };av_strerror(ret, err, sizeof(err) - 1);cout << err << endl;getchar();return -1;}cout << "音頻重采樣 上下文初始化成功" << endl;///3 音頻重采樣輸出空間分配AVFrame *pcm = av_frame_alloc();pcm->format = outSampleFmt;pcm->channels = channels;pcm->channel_layout = av_get_default_channel_layout(channels);pcm->nb_samples = 1024;//一幀音頻一通道的采樣數量ret = av_frame_get_buffer(pcm, 0); //給pcm分配存儲空間if (ret != 0){char err[1024] = { 0 };av_strerror(ret, err, sizeof(err) - 1);cout << err << endl;getchar();return -1;}///4 初始化音頻編碼器AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);if (!codec){cout << "avcodec_find_encoder failed!" << endl;getchar();return -1;}//音頻編碼器上下文AVCodecContext *ac = avcodec_alloc_context3(codec);if (!ac){cout << "avcodec_alloc_context3 failed!" << endl;getchar();return -1;}cout << "avcodec_alloc_context3 success!" << endl;ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;ac->thread_count = 8;//音頻的參數ac->bit_rate = 40000;ac->sample_rate = sampleRate;ac->sample_fmt = AV_SAMPLE_FMT_FLTP;ac->channels = channels;ac->channel_layout = av_get_default_channel_layout(channels);//打開編碼器ret = avcodec_open2(ac, 0, 0);if (ret != 0){char err[1024] = { 0 };av_strerror(ret, err, sizeof(err) - 1);cout << err << endl;getchar();return -1;}cout << "avcodec_open2 success!" << endl;///5 封裝器和音頻流配置//a.創建輸出封裝器上下文AVFormatContext *ic = NULL;ret = avformat_alloc_output_context2(&ic, 0, "flv", outUrl);if (ret != 0){char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);cout << buf << endl;getchar();return -1;}cout << "avformat_alloc_output_context2 success!" << endl;//b.添加音頻流AVStream *as = avformat_new_stream(ic, NULL);if (!as){throw exception("avformat_new_stream failed!");}cout << "avformat_new_stream success!" << endl;as->codecpar->codec_tag = 0;//從編碼器復制參數avcodec_parameters_from_context(as->codecpar, ac);av_dump_format(ic, 0, outUrl, 1);///6 打開rtmp的網絡輸出ioret = avio_open(&ic->pb, outUrl, AVIO_FLAG_WRITE);if (ret != 0){char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);cout << buf << endl;getchar();return -1;}//寫入封裝頭ret = avformat_write_header(ic, NULL);if (ret != 0){char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);cout << buf << endl;getchar();return -1;}cout << "avformat_write_header success!" << endl;//一次讀取一幀音頻的字節數int readSize = pcm->nb_samples*channels*sampleByte;char *buf = new char[readSize];int apts = 0;AVPacket pkt = {0};for (;;){//一次讀取一幀音頻if (input->bytesReady() < readSize){QThread::msleep(1);continue;}int size = 0;while (size != readSize){int len = io->read(buf + size, readSize - size);if (len < 0)break;size += len;}if (size != readSize)continue;const uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };indata[0] = (uint8_t *)buf;//已經讀取一幀源數據//重采樣數據int len = swr_convert(asc, pcm->data, pcm->nb_samples,//輸出參數,輸出存儲地址,樣本數indata, pcm->nb_samples);//pts運算//nb_sample/sample_rate =一幀音頻的秒數 //time_base pts=sec * timebase.den pcm->pts = apts;apts += av_rescale_q(pcm->nb_samples, {1,sampleRate},ac->time_base);int ret = avcodec_send_frame(ac, pcm);if (ret != 0)continue;av_packet_unref(&pkt);ret = avcodec_receive_packet(ac, &pkt);cout << "avcodec_receive_packet " << ret << endl;if (ret != 0)continue;cout << pkt.size << " " << flush;//推流pkt.pts = av_rescale_q(pkt.pts, ac->time_base, as->time_base);pkt.dts = av_rescale_q(pkt.dts, ac->time_base, as->time_base);pkt.duration= av_rescale_q(pkt.duration, ac->time_base, as->time_base);ret = av_interleaved_write_frame(ic, &pkt);if (ret == 0){cout << "#" << flush;}}delete buf;getchar();return a.exec(); }然后在這邊做個分享,由于我在第一次在函數?avcodec_receive_packe()沒有接收返回值,所以導致出現dump的情況,根據下面的打印可以發現第一次接收數據的時候返回的是-11,說明從緩存區獲取到的數據是有問題的,下面還對其進行推流就會出現錯誤的情況,具體的情況分析可以參考這篇文章關于FFmpeg編碼時,avcodec_receive_packet返回-11的解決辦法_小小菜鳥少少煩惱的博客-CSDN博客_avcodec_receive_packet
?
總結
以上是生活随笔為你收集整理的音视频开发系列(10):基于qt的音频推流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据加密以及国密基础知识
- 下一篇: 桌面快捷方式自动消失