FFMPEG音訊視訊開發: 開發本地視訊播放器(單執行緒解碼)(轉)
阿新 • • 發佈:2022-05-24
原文:https://xiaolong.blog.csdn.net/article/details/110621872
原始碼介紹
版本v1.
- 程式裡一共使用了2個執行緒,執行緒1是UI主執行緒,負責重新整理主介面的影象資料,影象資料顯示使用標籤控制元件;執行緒2是視訊解碼執行緒,負責解碼音訊資料和視訊資料,再將視訊圖片通過訊號傳送給主執行緒進行重新整理顯示,在主介面的影象顯示函式裡,獲取當前標籤控制元件的大小,自動調整影象的縮放。
- 音訊資料直接在視訊解碼執行緒裡播放
- 增加總時間顯示與當前時間顯示
- 增加任意跳轉功能
- 優化播放進度條顯示
- 優化播放器標籤的自動縮放問題,可以根據視窗大小自動縮放。
說明: 因為視訊解碼轉換,音訊解碼播放都是放在單個執行緒裡完成的,視訊尺寸太大就有些卡,小一些720P以下的到視訊是沒問題的。 後續增加多執行緒版本。
開發測試階段使用的視訊檔案都是MP4格式,播放MP4格式視訊很正常,其他格式未測試過,電腦上沒有其他格式的視訊檔案。
播放器執行效果
原始碼示例
widget.h檔案原始碼
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include "video_play.h" #include <QFileDialog> #include "config.h" #include <QListWidgetItem> #include <QDesktopWidget> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE //主執行緒 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void SetStyle(const QString &qssFile); void Log_Text_Display(QPlainTextEdit *plainTextEdit_log,QString text); bool max_flag=false; //最大化標誌 /* 定義視訊播放器的執行緒*/ class Thread_FFMPEG_LaLiu thread_laliu; private slots: void getCurrentTime(qint64 Sec); void GetSumTime(qint64 uSec); void Log_Display(QString text); void VideoDataDisplay(QImage image); void on_toolButton_Refresh_clicked(); void on_toolButton_Start_Play_clicked(bool checked); protected: void resizeEvent(QResizeEvent *event); //視窗大小變化事件 void closeEvent(QCloseEvent *event); //視窗關閉 bool eventFilter(QObject *obj, QEvent *event); private: Ui::Widget *ui; }; #endif // WIDGET_H
widget.cpp 原始碼
#include "widget.h" #include "ui_widget.h" #include <QDebug> /* * 設定QT介面的樣式 */ void Widget::SetStyle(const QString &qssFile) { QFile file(qssFile); if (file.open(QFile::ReadOnly)) { QString qss = QLatin1String(file.readAll()); qApp->setStyleSheet(qss); QString PaletteColor = qss.mid(20,7); qApp->setPalette(QPalette(QColor(PaletteColor))); file.close(); } else { qApp->setStyleSheet(""); } } Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); /*基本設定*/ this->SetStyle(":/images/blue.css"); //設定樣式表 this->setWindowIcon(QIcon(":/log.ico")); //設定圖示 this->setWindowTitle("視訊播放器"); ui->horizontalSlider_2->installEventFilter(this); //連線拉流執行緒的影象輸出訊號 connect(&thread_laliu,SIGNAL(VideoDataOutput(QImage )),this,SLOT(VideoDataDisplay(QImage ))); //連線拉流執行緒的日誌資訊 connect(&thread_laliu,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString))); //當前時間 connect(&thread_laliu,SIGNAL(sig_getCurrentTime(qint64)),this,SLOT(getCurrentTime(qint64))); //視訊總時間 connect(&thread_laliu,SIGNAL(sig_GetSumTime(qint64)),this,SLOT(GetSumTime(qint64))); audio_output_config.audio=QAudioDeviceInfo::defaultOutputDevice(); qDebug()<<"系統預設音效卡:"<<audio_output_config.audio.deviceName(); } Widget::~Widget() { delete ui; } //視訊重新整理顯示 void Widget::VideoDataDisplay(QImage image) { QPixmap my_pixmap; my_pixmap.convertFromImage(image); //設定 垂直居中 ui->label_ImageDisplay->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); ui->label_ImageDisplay->setPixmap(my_pixmap); } /*日誌顯示*/ void Widget::Log_Text_Display(QPlainTextEdit *plainTextEdit_log,QString text) { plainTextEdit_log->insertPlainText(text); //移動滾動條到底部 QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar(); if(scrollbar) { scrollbar->setSliderPosition(scrollbar->maximum()); } } //日誌顯示 void Widget::Log_Display(QString text) { //Log_Text_Display(ui->plainTextEdit_log,text); qDebug()<<text; } void Widget::on_toolButton_Refresh_clicked() { QString filename=QFileDialog::getOpenFileName(this,"選擇播放的視訊","D:/",tr("*.mp4 *.wmv *.*")); strncpy(video_audio_decode.rtmp_url,filename.toUtf8().data(),sizeof(video_audio_decode.rtmp_url)); //判斷執行緒是否正在執行 if(thread_laliu.isRunning()) { video_audio_decode.run_flag=0; thread_laliu.quit(); thread_laliu.wait(); } //開始執行執行緒 video_audio_decode.run_flag=1; //執行標誌 thread_laliu.start(); ui->toolButton_Start_Play->setText("停止播放"); } void Widget::on_toolButton_Start_Play_clicked(bool checked) { if(checked) //開始播放 { video_audio_decode.run_flag=2; //暫停播放 ui->toolButton_Start_Play->setText("繼續播放"); } else //停止播放 { video_audio_decode.run_flag=1; //繼續播放 ui->toolButton_Start_Play->setText("暫停播放"); } } /* 獲取視訊的時長 */ void Widget::GetSumTime(qint64 uSec) { qint64 Sec = uSec/1000000; //進度條 ui->horizontalSlider_2->setRange(0,Sec); QString mStr = QString("00%1").arg(Sec/60); QString sStr = QString("00%1").arg(Sec%60); QString str = QString("%1:%2").arg(mStr.right(2)).arg(sStr.right(2)); ui->label_SumTime->setText(str); } /* 獲取當前音訊時間 */ void Widget::getCurrentTime(qint64 Sec) { ui->horizontalSlider_2->setValue(Sec); QString mStr = QString("00%1").arg(Sec/60); QString sStr = QString("00%1").arg(Sec%60); QString str = QString("%1:%2").arg(mStr.right(2)).arg(sStr.right(2)); ui->label_CurrentTime->setText(str); } //視窗大小變化事件 void Widget::resizeEvent(QResizeEvent *event) { int height=this->geometry().height()-ui->horizontalSlider_2->height()*3-ui->toolButton_Refresh->height(); ui->label_ImageDisplay->setGeometry(0,0,this->width(),height); //獲取顯示視訊的標籤控制元件大小 video_audio_decode.label_size=ui->label_ImageDisplay->size(); } //視窗關閉事件 void Widget::closeEvent(QCloseEvent *event) { int ret = QMessageBox::question(this, tr("視訊播放器"), tr("是否需要退出程式?"),QMessageBox::Yes | QMessageBox::No); if(ret==QMessageBox::Yes) { video_audio_decode.run_flag=0; thread_laliu.quit(); thread_laliu.wait(); event->accept(); } else { event->ignore(); } /* 其中accept就是讓這個關閉事件通過並順利關閉視窗, ignore就是將其忽略回到視窗本身。這裡可千萬得注意在每一種可能性下都對event進行處理, 以免遺漏。 */ } bool Widget::eventFilter(QObject *obj, QEvent *event) { //解決QSlider點選不能到滑鼠指定位置的問題 if(obj==ui->horizontalSlider_2) { if (event->type()==QEvent::MouseButtonPress) //判斷型別 { QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); if (mouseEvent->button() == Qt::LeftButton) //判斷左鍵 { int value = QStyle::sliderValueFromPosition(ui->horizontalSlider_2->minimum(), ui->horizontalSlider_2->maximum(), mouseEvent->pos().x(), ui->horizontalSlider_2->width()); ui->horizontalSlider_2->setValue(value); //設定視訊跳轉 video_audio_decode.seek_pos=value* 1000000; //轉為微秒 video_audio_decode.seek_flag=1; } } } return QObject::eventFilter(obj,event); }
video_play.h原始碼
#ifndef VIDEO_PLAY_H
#define VIDEO_PLAY_H
#include "config.h"
//視訊音訊解碼執行緒
class Thread_FFMPEG_LaLiu: public QThread
{
Q_OBJECT
public:
QAudioOutput *audio_out;
QIODevice* audio_out_streamIn;
Thread_FFMPEG_LaLiu()
{
audio_out=nullptr;
audio_out_streamIn=nullptr;
}
void Audio_Out_Init();
int ffmpeg_rtmp_client();
protected:
void run();
signals:
void sig_GetSumTime(qint64 uSec);
void sig_getCurrentTime(qint64 Sec);
void LogSend(QString text);
void VideoDataOutput(QImage); //輸出訊號
};
//解碼拉流時的一些全域性引數
class VideoAudioDecode
{
public:
char rtmp_url[1024]; //播放的視訊地址
char run_flag; //2 表示暫停播放 1表示執行 0表示停止
bool seek_flag; //1 表示需要跳轉 0表示不需要跳轉
quint64 seek_pos; //跳轉的位置
QSize label_size;
};
extern class VideoAudioDecode video_audio_decode;
#endif // VIDEO_PLAY_H
video_play.cpp原始碼
#include "video_play.h"
#define MAX_AUDIO_FRAME_SIZE 1024
class VideoAudioDecode video_audio_decode;
class AudioOuputConfiguration audio_output_config;
//執行緒執行起點
void Thread_FFMPEG_LaLiu::run()
{
Audio_Out_Init();
LogSend("開始拉流.\n");
ffmpeg_rtmp_client();
}
//FFMPEG回撥函式,返回1表示超時 0表示正常
static int interrupt_cb(void *ctx)
{
if(video_audio_decode.run_flag==0)return 1;
return 0;
}
//拉流
int Thread_FFMPEG_LaLiu::ffmpeg_rtmp_client()
{
bool seek_flag=0;
int n;
double pts;
quint64 audio_clock; ///音訊時鐘
double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
AVStream *audio_stream; //音訊流
quint64 tmp_audio_clock=0; //儲存上一次的音訊時鐘
int video_width=0;
int video_height=0;
AVCodec *video_pCodec= nullptr;
AVCodec *audio_pCodec= nullptr;
// audio/video stream index
int video_stream_index = -1;
int audio_stream_index = -1;
AVFrame *PCM_pFrame = nullptr;
AVFrame *RGB24_pFrame = nullptr;
AVFrame *SRC_VIDEO_pFrame= nullptr;
uint8_t *out_buffer_rgb= nullptr;
int numBytes;
struct SwsContext *img_convert_ctx=nullptr; //用於解碼後的視訊格式轉換
AVPacket pkt;
int re;
bool send_flag=1;
AVPacket *packet;
//auido_out_format.setSampleRate(44100); //設定取樣率以對赫茲取樣。 以秒為單位,每秒採集多少聲音資料的頻率.
//auido_out_format.setChannelCount(1); //將通道數設定為通道。
//auido_out_format.setSampleSize(16); /*將樣本大小設定為指定的sampleSize(以位為單位)通常為8或16,但是某些系統可能支援更大的樣本量。*/
//auido_out_format.setCodec("audio/pcm"); //設定編碼格式
//auido_out_format.setByteOrder(QAudioFormat::LittleEndian); //樣本是小端位元組順序
//auido_out_format.setSampleType(QAudioFormat::SignedInt); //樣本型別
//設定音訊轉碼後輸出相關引數
//取樣的佈局方式
uint64_t out_channel_layout = AV_CH_LAYOUT_MONO; //單聲道音訊佈局
//取樣個數
int out_nb_samples = MAX_AUDIO_FRAME_SIZE;
//取樣格式
enum AVSampleFormat sample_fmt = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16;
//取樣率
int out_sample_rate = 44100;
//通道數
int out_channels;
int buffer_size;
uint8_t *buffer;
int64_t in_channel_layout;
struct SwrContext *convert_ctx;
// Allocate an AVFormatContext
AVFormatContext* format_ctx = avformat_alloc_context();
format_ctx->interrupt_callback.callback = interrupt_cb; //--------註冊回撥函式
// 開啟rtsp:開啟輸入流並讀取標題。 編解碼器未開啟
const char* url =video_audio_decode.rtmp_url;// "rtmp://193.112.142.152:8888/live/abcd";
LogSend(tr("播放的視訊檔案: %1\n").arg(url));
int ret = -1;
ret = avformat_open_input(&format_ctx, url, nullptr, nullptr);
if(ret != 0)
{
LogSend(tr("無法開啟視訊檔案: %1, return value: %2 \n").arg(url).arg(ret));
goto ERROR;
}
// 讀取媒體檔案的資料包以獲取流資訊
ret = avformat_find_stream_info(format_ctx, nullptr);
if(ret < 0)
{
LogSend(tr("無法獲取流資訊: %1\n").arg(ret));
return -1;
}
LogSend(tr("視訊中流的數量: %1\n").arg(format_ctx->nb_streams));
for(int i = 0; i < format_ctx->nb_streams; ++i)
{
const AVStream* stream = format_ctx->streams[i];
LogSend(tr("編碼資料的型別: %1\n").arg(stream->codecpar->codec_id));
if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
//查詢解碼器
video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);
//開啟解碼器
int err = avcodec_open2(stream->codec,video_pCodec,nullptr);
if(err!=0)
{
LogSend(tr("H264解碼器開啟失敗.\n"));
return 0;
}
video_stream_index = i;
//得到視訊幀的寬高
video_width=stream->codecpar->width;
video_height=stream->codecpar->height;
LogSend(tr("視訊幀的尺寸(以畫素為單位): (寬X高)%1x%2 畫素格式: %3\n").arg(
stream->codecpar->width).arg(stream->codecpar->height).arg(stream->codecpar->format));
}
else if(stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_stream=format_ctx->streams[i];
audio_stream_index = i;
//查詢解碼器
audio_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);
qDebug()<<"codec_id:"<<stream->codecpar->codec_id<<"AV_CODEC_ID_AAC:"<<AV_CODEC_ID_AAC;
//開啟解碼器
int err = avcodec_open2(stream->codec,audio_pCodec, nullptr);
if(err!=0)
{
LogSend(tr("音訊解碼器開啟失敗.\n"));
return 0;
}
}
}
if (video_stream_index == -1)
{
LogSend("沒有檢測到視訊流.\n");
return -1;
}
if (audio_stream_index == -1)
{
LogSend("沒有檢測到音訊流.\n");
}
//初始化音訊解碼相關的引數
PCM_pFrame = av_frame_alloc();// 存放解碼後PCM資料的緩衝區
//建立packet,用於儲存解碼前音訊的資料
packet = (AVPacket *)malloc(sizeof(AVPacket));
av_init_packet(packet);
//通道數
out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
//得到每幀音訊資料大小
buffer_size = av_samples_get_buffer_size(nullptr, out_channels, out_nb_samples, sample_fmt, 1);
//建立buffer,注意要用av_malloc---存放轉碼後的資料
buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
in_channel_layout = av_get_default_channel_layout(format_ctx->streams[audio_stream_index]->codec->channels);
//開啟轉碼器
// convert_ctx = swr_alloc();
//設定轉碼引數
convert_ctx = swr_alloc_set_opts(nullptr, out_channel_layout, sample_fmt, out_sample_rate, \
in_channel_layout, format_ctx->streams[audio_stream_index]->codec->sample_fmt, format_ctx->streams[audio_stream_index]->codec->sample_rate, 0, nullptr);
//轉碼後的資料
LogSend(tr("轉碼_nb_samples=%1\n").arg(out_nb_samples)); //此幀描述的音訊樣本數(每通道
LogSend(tr("轉碼_音訊資料聲道=%1\n").arg(out_channels)); //聲道數量
LogSend(tr("轉碼_音訊資料取樣率=%1\n").arg(out_sample_rate)); //取樣率
LogSend(tr("轉碼_channel_layout=%1\n").arg(out_channel_layout)); //通道佈局
//引數1:重取樣上下文
//引數2:輸出的layout
//引數3:輸出的樣本格式。Float, S16, S24
//引數4:輸出的樣本率。可以不變。
//引數5:輸入的layout。
//引數6:輸入的樣本格式。
//引數7:輸入的樣本率。
//引數8,引數9,日誌,不用管,可直接傳0
//初始化轉碼器
swr_init(convert_ctx);
/*設定視訊轉碼器*/
SRC_VIDEO_pFrame = av_frame_alloc();
RGB24_pFrame = av_frame_alloc();// 存放解碼後YUV資料的緩衝區
out_buffer_rgb=nullptr; //解碼後的rgb資料
//這裡改成了 將解碼後的YUV資料轉換成RGB24
img_convert_ctx = sws_getContext(video_width, video_height,
format_ctx->streams[video_stream_index]->codec->pix_fmt,video_width, video_height,
AV_PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr);
numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,video_width,video_height);
out_buffer_rgb = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) RGB24_pFrame, out_buffer_rgb, AV_PIX_FMT_RGB24,
video_width, video_height);
//獲取視訊的總時長
emit sig_GetSumTime(format_ctx->duration);
while(video_audio_decode.run_flag)
{
if(video_audio_decode.run_flag==2)
{
msleep(100); //暫停播放
continue; //繼續執行
}
//判斷是否要執行跳轉
if(video_audio_decode.seek_flag)
{
video_audio_decode.seek_flag=0;
seek_flag=1;
int64_t seek_target = video_audio_decode.seek_pos;
AVRational aVRational = {1, AV_TIME_BASE};
if(video_stream_index >= 0)
{
seek_target = av_rescale_q(seek_target, aVRational,
format_ctx->streams[video_stream_index]->time_base);
}
qDebug()<<"跳轉成功:"<<seek_target<<",狀態:"<<av_seek_frame(format_ctx, video_stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
//重新整理解碼器
avcodec_flush_buffers(format_ctx->streams[video_stream_index]->codec);
}
//讀取一幀資料
ret=av_read_frame(format_ctx, &pkt);
if(ret < 0)
{
qDebug()<<"資料讀取完畢.";
break;
}
//得到音訊包
if(pkt.stream_index == audio_stream_index)
{
//解碼聲音
re = avcodec_send_packet(format_ctx->streams[audio_stream_index]->codec,&pkt);//傳送視訊幀
if (re != 0)
{
av_packet_unref(&pkt);//不成功就釋放這個pkt
continue;
}
re = avcodec_receive_frame(format_ctx->streams[audio_stream_index]->codec, PCM_pFrame);//接受後對視訊幀進行解碼
if (re != 0)
{
av_packet_unref(&pkt);//不成功就釋放這個pkt
continue;
}
//轉碼 針對每一幀音訊的處理。把一幀幀的音訊作相應的重取樣
swr_convert(convert_ctx, &buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)PCM_pFrame->data, PCM_pFrame->nb_samples);
//只發送一次
if(send_flag)
{
send_flag=0;
//得到PCM資料的配置資訊
LogSend(tr("原始PCM資料_nb_samples=%1\n").arg(PCM_pFrame->nb_samples)); //此幀描述的音訊樣本數(每通道
LogSend(tr("原始PCM資料_音訊資料聲道=%1\n").arg(PCM_pFrame->channels)); //聲道數量 out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
LogSend(tr("原始PCM資料_音訊資料取樣率=%1\n").arg(PCM_pFrame->sample_rate)); //取樣率
LogSend(tr("原始PCM資料_channel_layout=%1\n").arg(PCM_pFrame->channel_layout)); //通道佈局
}
//得到音訊時間
if (pkt.pts != AV_NOPTS_VALUE)
{
audio_clock = av_q2d(audio_stream->time_base) * pkt.pts;
}
//已經跳轉過
if(seek_flag)
{
//如果當前時鐘小於跳轉的時鐘,就釋放當前這幾幀資料
if(audio_clock*1000000<video_audio_decode.seek_pos)
{
av_packet_unref(&pkt);
continue;
}
else
{
seek_flag=0;
}
}
//每次以秒為單位向主介面傳送訊號
if(tmp_audio_clock!=audio_clock)
{
tmp_audio_clock=audio_clock;
emit sig_getCurrentTime(audio_clock);
}
if(!audio_output_config.audio.isNull())
{
//音訊播放
while(audio_out_streamIn->write((const char *)buffer,buffer_size)!=buffer_size)
{
}
}
}
if(pkt.stream_index == video_stream_index)
{
//解碼視訊 frame
re = avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt);//傳送視訊幀
if (re != 0)
{
av_packet_unref(&pkt);//不成功就釋放這個pkt
continue;
}
re = avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame);//接受後對視訊幀進行解碼
if (re != 0)
{
av_packet_unref(&pkt);//不成功就釋放這個pkt
continue;
}
//轉格式
sws_scale(img_convert_ctx,
(uint8_t const **) SRC_VIDEO_pFrame->data,
SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,
RGB24_pFrame->linesize);
//載入圖片資料
QImage image(out_buffer_rgb,video_width,video_height,QImage::Format_RGB888);
image=image.scaled(video_audio_decode.label_size,Qt::KeepAspectRatio, Qt::SmoothTransformation);
// VideoDataOutput(image.scaled(640,480,Qt::KeepAspectRatio, Qt::SmoothTransformation)); //傳送訊號
VideoDataOutput(image); //傳送訊號
}
av_packet_unref(&pkt);
}
ERROR:
if(SRC_VIDEO_pFrame)av_free(SRC_VIDEO_pFrame);
if(RGB24_pFrame)av_free(RGB24_pFrame);
if(out_buffer_rgb) av_free(out_buffer_rgb);
if(img_convert_ctx)sws_freeContext(img_convert_ctx);
if(format_ctx)
{
avformat_close_input(&format_ctx);//釋放解封裝器的空間,以防空間被快速消耗完
avformat_free_context(format_ctx);
}
LogSend("視訊音訊解碼播放器的執行緒退出成功.\n");
LogSend("play exit\n");
return 0;
}
//音訊輸出初始化
void Thread_FFMPEG_LaLiu::Audio_Out_Init()
{
QAudioFormat auido_out_format;
//設定錄音的格式
auido_out_format.setSampleRate(44100); //設定取樣率以對赫茲取樣。 以秒為單位,每秒採集多少聲音資料的頻率.
auido_out_format.setChannelCount(1); //將通道數設定為通道。
auido_out_format.setSampleSize(16); /*將樣本大小設定為指定的sampleSize(以位為單位)通常為8或16,但是某些系統可能支援更大的樣本量。*/
auido_out_format.setCodec("audio/pcm"); //設定編碼格式
auido_out_format.setByteOrder(QAudioFormat::LittleEndian); //樣本是小端位元組順序
auido_out_format.setSampleType(QAudioFormat::SignedInt); //樣本型別
if(audio_output_config.audio.isNull())return;
QAudioDeviceInfo info(audio_output_config.audio);
if(audio_out)
{
delete audio_out;
audio_out=nullptr;
}
audio_out = new QAudioOutput(info,auido_out_format);
audio_out_streamIn=audio_out->start();
LogSend("音訊輸出初始化成功.\n");
}