#include "playthread.h" playthread::playthread() { audio=NULL; type = control_none; } bool playthread::initAudio(int SampleRate) { QAudioFormat format; if(audio!=NULL) return true; format.setSampleRate(SampleRate); //設置採樣率 format.setChannelCount(2); //設置通道數 format.setSampleSize(16); //樣本數據16位 format.setCodec("audio/pcm"); //播出格式爲pcm格式 format.setByteOrder(QAudioFormat::LittleEndian); //默認小端模式 format.setSampleType(QAudioFormat::UnSignedInt); //無符號整形數 QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); //選擇默認輸出設備 // foreach(int count,info.supportedChannelCounts()) // { // qDebug()<<"輸出設備支持的通道數:"<<count; // } // foreach(int count,info.supportedSampleRates()) // { // qDebug()<<"輸出設備支持的採樣率:"<<count; // } // foreach(int count,info.supportedSampleSizes()) // { // qDebug()<<"輸出設備支持的樣本數據位數:"<<count; // } if (!info.isFormatSupported(format)) { qDebug()<<"輸出設備不支持該格式,不能播放音頻"; return false; } audio = new QAudioOutput(format, this); audio->setBufferSize(100000); return true; } void playthread::play(QString filePath) { this->filePath = filePath; type = control_play; if(!this->isRunning()) { this->start(); } } void playthread::stop() { if(this->isRunning()) { type = control_stop; } } void playthread::pause() { if(this->isRunning()) { type = control_pause; } } void playthread::resume() { if(this->isRunning()) { type = control_resume; } } void playthread::seek(int value) { if(this->isRunning()) { seekMs = value; type = control_seek; } } void playthread::debugErr(QString prefix, int err) //根據錯誤編號獲取錯誤信息並打印 { char errbuf[512]={0}; av_strerror(err,errbuf,sizeof(errbuf)); qDebug()<<prefix<<":"<<errbuf; emit ERROR(prefix+":"+errbuf); } bool playthread::runIsBreak() //處理控制,判斷是否須要中止 { bool ret = false; //處理播放暫停 if(type == control_pause) { while(type == control_pause) { audio->suspend(); msleep(500); } if(type == control_resume) { audio->resume(); } } if(type == control_play) //從新播放 { ret = true; if(audio->state()== QAudio::ActiveState) audio->stop(); } if(type == control_stop) //中止 { ret = true; if(audio->state()== QAudio::ActiveState) audio->stop(); } return ret; } void playthread::runPlay() { int ret; int destMs,currentMs; if(audio==NULL) { emit ERROR("輸出設備不支持該格式,不能播放音頻"); return ; } //初始化網絡庫 (能夠打開rtsp rtmp http 協議的流媒體視頻) avformat_network_init(); AVFormatContext *pFmtCtx=NULL; ret = avformat_open_input(&pFmtCtx, this->filePath.toLocal8Bit().data(),NULL, NULL) ; //打開音視頻文件並建立AVFormatContext結構體以及初始化. if (ret!= 0) { debugErr("avformat_open_input",ret); return ; } ret = avformat_find_stream_info(pFmtCtx, NULL); //初始化流信息 if (ret!= 0) { debugErr("avformat_find_stream_info",ret); return ; } int audioindex=-1; audioindex = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); qDebug()<<"audioindex:"<<audioindex; AVCodec *acodec = avcodec_find_decoder(pFmtCtx->streams[audioindex]->codecpar->codec_id);//獲取codec AVCodecContext *acodecCtx = avcodec_alloc_context3(acodec); //構造AVCodecContext ,並將vcodec填入AVCodecContext中 avcodec_parameters_to_context(acodecCtx, pFmtCtx->streams[audioindex]->codecpar); //初始化AVCodecContext ret = avcodec_open2(acodecCtx, NULL,NULL); //打開解碼器,因爲以前調用avcodec_alloc_context3(vcodec)初始化了vc,那麼codec(第2個參數)能夠填NULL if (ret!= 0) { debugErr("avcodec_open2",ret); return ; } SwrContext *swrctx =NULL; swrctx=swr_alloc_set_opts(swrctx, av_get_default_channel_layout(2),AV_SAMPLE_FMT_S16,44100, acodecCtx->channel_layout, acodecCtx->sample_fmt,acodecCtx->sample_rate, NULL,NULL); swr_init(swrctx); destMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)*1000*pFmtCtx->streams[audioindex]->duration; qDebug()<<"碼率:"<<acodecCtx->bit_rate; qDebug()<<"格式:"<<acodecCtx->sample_fmt; qDebug()<<"通道:"<<acodecCtx->channels; qDebug()<<"採樣率:"<<acodecCtx->sample_rate; qDebug()<<"時長:"<<destMs; qDebug()<<"解碼器:"<<acodec->name; AVPacket * packet =av_packet_alloc(); AVFrame *frame =av_frame_alloc(); audio->stop(); QIODevice*io = audio->start(); while(1) { if(runIsBreak()) break; if(type == control_seek) { av_seek_frame(pFmtCtx, audioindex, seekMs/(double)1000/av_q2d(pFmtCtx->streams[audioindex]->time_base),AVSEEK_FLAG_BACKWARD); type = control_none; emit seekOk(); } ret = av_read_frame(pFmtCtx, packet); if (ret!= 0) { debugErr("av_read_frame",ret); emit duration(destMs,destMs); break ; } //解碼一幀數據 ret = avcodec_send_packet(acodecCtx, packet); av_packet_unref(packet); if (ret != 0) { debugErr("avcodec_send_packet",ret); continue ; } if(packet->stream_index==audioindex) { while( avcodec_receive_frame(acodecCtx, frame) == 0) { if(runIsBreak()) break; uint8_t *data[2] = { 0 }; int byteCnt=frame->nb_samples * 2 * 2; unsigned char *pcm = new uint8_t[byteCnt]; //frame->nb_samples*2*2表示分配樣本數據量*兩通道*每通道2字節大小 data[0] = pcm; //輸出格式爲AV_SAMPLE_FMT_S16(packet類型),因此轉換後的LR兩通道都存在data[0]中 ret = swr_convert(swrctx, data, frame->nb_samples, //輸出 (const uint8_t**)frame->data,frame->nb_samples ); //輸入 //將重採樣後的data數據發送到輸出設備,進行播放 while (audio->bytesFree() < byteCnt) { if(runIsBreak()) break; msleep(10); } if(!runIsBreak()) io->write((const char *)pcm,byteCnt); currentMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)*1000*frame->pts; //qDebug()<<"時長:"<<destMs<<currentMs; emit duration(currentMs,destMs); delete[] pcm; } } } //釋放內存 av_frame_free(&frame); av_packet_free(&packet); swr_free(&swrctx); avcodec_free_context(&acodecCtx); avformat_close_input(&pFmtCtx); } void playthread::run() { if(!initAudio(44100)) { emit ERROR("輸出設備不支持該格式,不能播放音頻"); } while(1) { switch(type) { case control_none: msleep(100); break; case control_play : type=control_none;runPlay(); break; //播放 default: type=control_none; break; } } }
4.2 widget界面類html
而在界面中要處理的就很簡單,widget.cpp以下所示:網絡
#include "widget.h" #include "ui_widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); this->setAcceptDrops(true); thread = new playthread(); connect(thread,SIGNAL(duration(int,int)),this,SLOT(onDuration(int,int))); connect(thread,SIGNAL(seekOk()),this,SLOT(onSeekOk())); void duration(long currentMs,long destMs); //播放時長 thread->start(); sliderSeeking =false; } Widget::~Widget() { delete ui; thread->stop(); } void Widget::onSeekOk() { sliderSeeking=false; } void Widget::onDuration(int currentMs,int destMs) //時長 { static int currentMs1=-1,destMs1=-1; if(currentMs1==currentMs&&destMs1==destMs) { return; } currentMs1 = currentMs; destMs1 = destMs; qDebug()<<"onDuration:"<<currentMs<<destMs<<sliderSeeking; QString currentTime = QString("%1:%2:%3").arg(currentMs1/360000%60,2,10,QChar('0')).arg(currentMs1/6000%60,2,10,QChar('0')).arg(currentMs1/1000%60,2,10,QChar('0')); QString destTime = QString("%1:%2:%3").arg(destMs1/360000%60,2,10,QChar('0')).arg(destMs1/6000%60,2,10,QChar('0')).arg(destMs1/1000%60,2,10,QChar('0')); ui->label_duration->setText(currentTime+"/"+destTime); if(!sliderSeeking) //未滑動 { ui->slider->setMaximum(destMs); ui->slider->setValue(currentMs); } } void Widget::dragEnterEvent(QDragEnterEvent *event) { if(event->mimeData()->hasUrls()) //判斷拖的類型 { event->acceptProposedAction(); } else { event->ignore(); } } void Widget::dropEvent(QDropEvent *event) { if(event->mimeData()->hasUrls()) //判斷放的類型 { QList<QUrl> List = event->mimeData()->urls(); if(List.length()!=0) { ui->line_audioPath->setText(List[0].toLocalFile()); } } else { event->ignore(); } } void Widget::on_btn_start_clicked() { sliderSeeking=false; thread->play(ui->line_audioPath->text()); } void Widget::on_btn_stop_clicked() { thread->stop(); } void Widget::on_btn_pause_clicked() { thread->pause(); } void Widget::on_btn_resume_clicked() { thread->resume(); } void Widget::on_slider_sliderPressed() { sliderSeeking=true; } void Widget::on_slider_sliderReleased() { thread->seek(ui->slider->value()); }