Qt+SDL+ffmpeg 实现音视频播放器

3 篇文章 1 订阅
2 篇文章 2 订阅

目录

主要思路

效果

​核心代码

解码:

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 领取。   

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值