目录
主要思路
sdl用于视频的渲染和音频的播放。qt实现播放器客户端,而ffmpeg则用于解码音视频。
关于sdl的了解可以参考https://blog.youkuaiyun.com/c_shell_python/article/details/109521840
关于ffmpeg,可以参考雷神的博客https://blog.youkuaiyun.com/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 领取。