目錄
主要思路
效果
?核心代碼
解碼:
SDL初始化:
視頻幀渲染:
完整工程:
主要思路
sdl用于視頻的渲染和音頻的播放,qt實作播放器客戶端,而ffmpeg則用于解碼音視頻,
關于sdl的了解可以參考https://blog.csdn.net/c_shell_python/article/details/109521840
關于ffmpeg,可以參考雷神的博客https://blog.csdn.net/leixiaohua1020/article/details/15811977
這里就不過多的贅述,
本篇主要是讓初學者了解一個最簡單的桌面視頻播放器是如何制作的,因為qt框架的跨平臺特性,所以采用qt來實作,
通過ffmpeg解碼視頻,將視頻資料通過信號槽傳遞給sdl繪制,音頻資料通過回呼函式呼叫sdl的介面進行播放,
效果

核心代碼
解碼:
?
void VideoDecode::run()
{
if (_url.isEmpty()) return;
av_register_all();
avformat_network_init();
_pFormatCtx = avformat_alloc_context();
AVDictionary* opts = NULL;
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
av_dict_set(&opts, "stimeout", "10000000", 0);//設定打開10秒超時
av_dict_set(&opts, "buffer_size", "1024000", 0);
if (avformat_open_input(&_pFormatCtx, _url.toStdString().c_str(), NULL, &opts) != 0)
{
qDebug() << "avformat_open_input failed " << _url;
return;
}
av_dict_free(&opts);
if (avformat_find_stream_info(_pFormatCtx, NULL) < 0)
{
qDebug() << "avformat_find_stream_info failed";
return;
}
//視頻
_videoIndex = -1;
for (int i = 0; i < _pFormatCtx->nb_streams; ++i)
{
if (_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
_videoIndex = i;
break;
}
}
if (_videoIndex == -1)
{
qDebug() << "find video stream failed";
return;
}
//音頻
_audioIndex = ERROR;
for (int i = 0; i < _pFormatCtx->nb_streams; ++i)
{
if (_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
_audioIndex = i;
break;
}
}
if (_audioIndex == ERROR) //沒有音頻也繼續
{
qDebug() << "find audio stream failed";
}
else
{
_aCodecCtx = _pFormatCtx->streams[_audioIndex]->codec;
_aCodec = avcodec_find_decoder(_aCodecCtx->codec_id);
if (NULL == _aCodec)
{
qDebug() << "Can not find audio decoder!";
}
if (avcodec_open2(_aCodecCtx, _aCodec, NULL) < 0)
{
qDebug() << "Could not open audio codec!";
}
_aAvFrame = av_frame_alloc();
}
_pCodecCtx = _pFormatCtx->streams[_videoIndex]->codec;
_pCodec = avcodec_find_decoder(_pCodecCtx->codec_id);
if (_pCodec == NULL)
{
qDebug() << "find_decoder failed !";
return;
}
_pCodecCtx->thread_count = 8;
_pCodecCtx->thread_type = FF_THREAD_SLICE;
if (avcodec_open2(_pCodecCtx, _pCodec, NULL) < 0)
{
qDebug() << "open2 decoder failed";
return;
}
int width = _pCodecCtx->width;
int height = _pCodecCtx->height;
emit sigUpdateTexture(width, height);
qDebug() << "width:" << width << "height" << height;
if (width == 0 || height == 0) {
return;
}
_pAvFrame = av_frame_alloc();
_pFrameYUV = av_frame_alloc(); //存盤解碼后轉換的yuv資料
int size = avpicture_get_size(AV_PIX_FMT_YUV420P, _pCodecCtx->width, _pCodecCtx->height);
_out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)_pFrameYUV, _out_buffer, AV_PIX_FMT_YUV420P, _pCodecCtx->width, _pCodecCtx->height);
_packet = (AVPacket*)malloc(sizeof(AVPacket));
av_dump_format(_pFormatCtx, 0, _url.toStdString().c_str(), 0);
bool isHasAudioParams = false;
if (_audioIndex != ERROR) {
while (true)
{
if (av_read_frame(_pFormatCtx, _packet) >= 0)
{
int got_picture;
if (_audioIndex == _packet->stream_index)
{
if (avcodec_decode_audio4(_aCodecCtx, _aAvFrame, &got_picture, _packet) < 0)
{
qDebug() << "Error in decoding audio frame!";
break;
}
//只解1幀
if (got_picture > 0)
{
//_out_sample_fmt = _aCodecCtx->sample_fmt;
_out_sample_rate = _aCodecCtx->sample_rate;
_out_channels = _aCodecCtx->channels;
_out_chn_layout = _aCodecCtx->channel_layout;
isHasAudioParams = true;
break;
}
}
av_free_packet(_packet);
}
}
}
if (isHasAudioParams)
setAudioParams();
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(_pCodecCtx->width, _pCodecCtx->height, _pCodecCtx->pix_fmt,
_pCodecCtx->width, _pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
int ret;
while (1)
{
if (_stop)
{
break;
}
if (av_read_frame(_pFormatCtx, _packet) >= 0)
{
if (_videoIndex == _packet->stream_index)
{
int got_picture_res;
//新
//@return 0 on success, otherwise negative error code
if (0 != avcodec_send_packet(_pCodecCtx, _packet))
{
qDebug() << "avcodec_send_packet error";
break;
}
if (0 != avcodec_receive_frame(_pCodecCtx, _pAvFrame))
{
qDebug() << "avcodec_receive_frame error";
got_picture_res = 0;
//break;
}else
got_picture_res = 1;
//舊
//if (avcodec_decode_video2(_pCodecCtx, _pAvFrame, &got_picture_res, _packet) < 0)
//{
// qDebug() << "avcodec_decode_video2 decode error";
// break;
//}
if (got_picture_res)
{
sws_scale(img_convert_ctx, (const uint8_t* const*)_pAvFrame->data, _pAvFrame->linesize, 0,
_pCodecCtx->height, _pFrameYUV->data, _pFrameYUV->linesize);
emit sigUpdate((uchar*)_pFrameYUV->data[0], _pFrameYUV->linesize[0],
(uchar*)_pFrameYUV->data[1], _pFrameYUV->linesize[1],
(uchar*)_pFrameYUV->data[2], _pFrameYUV->linesize[2]);
}
}
else if (_audioIndex == _packet->stream_index)
{
int got_picture_res;
//新
if (0 != avcodec_send_packet(_aCodecCtx, _packet))
{
qDebug() << "avcodec_send_packet error";
continue;
}
if (0 != avcodec_receive_frame(_aCodecCtx, _aAvFrame))
{
qDebug() << "avcodec_receive_frame error";
got_picture_res = 0;
//break;
}
else
got_picture_res = 1;
//舊
//if (avcodec_decode_audio4(_aCodecCtx, _aAvFrame, &got_picture_res, _packet) < 0)
//{
// qDebug() << "Error in decoding audio frame!";
// //break;
// //goto OUT;
// continue;
//}
if (got_picture_res > 0)
{
memset(outBuff, 0, MAX_AUDIO_FRAME_SIZE);
out_buffer_size = av_samples_get_buffer_size(
NULL,
_out_channels,
_aAvFrame->nb_samples,
_out_sample_fmt,
1);
//轉換
if (_aAvFrame->format != AV_SAMPLE_FMT_S16) {
int ret = swr_convert(
_au_convert_ctx,
&outBuff,
MAX_AUDIO_FRAME_SIZE,
(const uint8_t **)_aAvFrame->data,
_aAvFrame->nb_samples
);
}
else
{
memcpy(outBuff, (uint8_t *)_aAvFrame->data[0], out_buffer_size);
//這樣寫 某些流會有雜音
//memcpy(outBuff, (uint8_t *)_aAvFrame->data[0], _aAvFrame->linesize[0]);
//out_buffer_size = _aAvFrame->linesize[0];
}
audioChunk = (Uint8*)outBuff;
audioLen = out_buffer_size;
while (audioLen > 0)//等待直到音頻資料播放完畢!
SDL_Delay(1);
}
}
av_free_packet(_packet);
}
else
{
break;
}
}
}
?
SDL初始化:
void VideoWidget::SDLInit()
{
// 初始化sdl
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { // 目前只需要播放視頻
qDebug() << "SDL could not initialized with error: " << SDL_GetError();
return;
}
// 創建表單
_window = SDL_CreateWindowFrom((void*)this->winId());
if (!_window) {
qDebug() << "SDL_CreateWindowFrom failed";
return;
}
// 從表單創建渲染器
_renderer = SDL_CreateRenderer(_window, -1, 0);
int width = this->width();
int height = this->height();
}
視頻幀渲染:
void VideoWidget::slotUpdate(uchar*data0, int linesize0,
uchar*data1, int linesize1, uchar*data2, int linesize2)
{
int result = SDL_UpdateYUVTexture(_texture, NULL, data0, linesize0,
data1, linesize1, data2, linesize2);
if (result != 0) {
qDebug() << "SDL_UpdateTexture failed";
return;
}
SDL_RenderClear(_renderer);
SDL_RenderCopy(_renderer, _texture, NULL, NULL);
SDL_RenderPresent(_renderer);
}
完整工程:
如需要完整源代碼,可以關注我的公眾號 編程騎士 回復 ffmpeg 領取,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/275553.html
標籤:其他
