ilostmyid2 Ответов: 1

Каков наилучший метод рендеринга декодированного кадра?


В следующем коде мне нужно знать, использовал ли я правильный метод для рендеринга кадров, полученных после декодирования и масштабирования. Кроме того, я получаю исключение. Я не знаю, что это за исключение, так как через некоторое время я вижу красный крест вместо изображений.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using FFmpeg.AutoGen;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing.Imaging;

namespace player.cs
{
	public partial class Form1 : Form
	{
		private bool _bPlaying, _bInfoCtrlsUpdated;
		private string _path;
		private string _resolution;
		private string _timebase;
		private string _codecId;
		private Bitmap _bmp;
		
		public Form1()
		{
			InitializeComponent();
			backgroundWorker1.DoWork += backgroundWorker1_DoWork;
			backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
			_path = "1.mp4";
			tbPath.Text = _path;
		}

		unsafe void PlayVideoFile(DoWorkEventArgs e)
		{
			AVBufferRef* hw_device_ctx;
			Debug.Assert(FFmpegInvoke.av_hwdevice_ctx_create(&hw_device_ctx,
							 FFmpeg.AutoGen.AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2, null, null, 0) == 0);
			AVFormatContext* input_ctx = null;
			Debug.Assert(FFmpegInvoke.avformat_open_input(&input_ctx, _path, null, null) == 0);
			Debug.Assert(FFmpegInvoke.avformat_find_stream_info(input_ctx, null) >= 0);
			AVCodec* decoder = null;
			int video_stream = FFmpegInvoke.av_find_best_stream(input_ctx, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
			Debug.Assert(video_stream >= 0);
			AVCodecContext* decoder_ctx = null;
			decoder_ctx = FFmpegInvoke.avcodec_alloc_context3(decoder);
			Debug.Assert(decoder_ctx != null);
			AVStream* video = null;
			video = input_ctx->streams[video_stream];
			Debug.Assert(FFmpegInvoke.avcodec_parameters_to_context(decoder_ctx, video->codecpar) >= 0);
			decoder_ctx->hw_device_ctx = FFmpegInvoke.av_buffer_ref(hw_device_ctx);
			Debug.Assert(FFmpegInvoke.avcodec_open2(decoder_ctx, decoder, null) >= 0);
			AVFrame* frame = FFmpegInvoke.av_frame_alloc();
			Debug.Assert(frame != null);
			AVPacket packet;
			_resolution = string.Format("{0}x{1}", decoder_ctx->width, decoder_ctx->height);
			_timebase = string.Format("{0}/{1}", decoder_ctx->time_base.num, decoder_ctx->time_base.den);
			_codecId = decoder_ctx->codec_id.ToString();
			AVPixelFormat convertToPixFmt = AVPixelFormat.AV_PIX_FMT_RGBA;
			int dest_width = pictureBox1.Width, dest_height = pictureBox1.Width;
			SwsContext* convertContext = FFmpegInvoke.sws_getContext(
				decoder_ctx->width, decoder_ctx->height, AVPixelFormat.AV_PIX_FMT_YUV420P,
				dest_width, dest_height, convertToPixFmt,
				FFmpegInvoke.SWS_FAST_BILINEAR, null, null, null);
			AVFrame* convertedFrame = FFmpegInvoke.av_frame_alloc();
			int convertedFrameAspectBufferSize = FFmpegInvoke.avpicture_get_size(convertToPixFmt, dest_width, dest_height);
			void* convertedFrameBuffer = FFmpegInvoke.av_malloc((uint)convertedFrameAspectBufferSize);
			FFmpegInvoke.avpicture_fill((AVPicture*)convertedFrame, (byte*)convertedFrameBuffer, convertToPixFmt, dest_width, dest_height);
			while (true)
			{
				while (true)
				{
					backgroundWorker1.ReportProgress((int)(video->cur_dts * 100 / video->duration));
					if (backgroundWorker1.CancellationPending)
						break;
					int ret = FFmpegInvoke.av_read_frame(input_ctx, &packet);
					if (ret == FFmpegInvoke.AVERROR_EOF)
						break;
					Debug.Assert(ret >= 0);
					if (video_stream != packet.stream_index)
						continue;
					ret = FFmpegInvoke.avcodec_send_packet(decoder_ctx, &packet);
					Debug.Assert(ret >= 0);
					ret = FFmpegInvoke.avcodec_receive_frame(decoder_ctx, frame);
					if (ret < 0)
						continue;
					ret = FFmpegInvoke.sws_scale(
						convertContext,
						&frame->data_0,
						frame->linesize,
						0,
						frame->height,
						&convertedFrame->data_0,
						convertedFrame->linesize);
					Debug.Assert(ret >= 0);
					var bmp = new Bitmap(
						dest_width,
						dest_height,
						convertedFrame->linesize[0],
						PixelFormat.Format32bppPArgb,
						new IntPtr(convertedFrame->data_0));
					pictureBox1.Image = bmp;
				}
				if (backgroundWorker1.CancellationPending)
				{
					e.Cancel = true;
					break;
				}
				Debug.Assert(FFmpegInvoke.av_seek_frame(input_ctx, video_stream, 0, FFmpegInvoke.AVSEEK_FLAG_FRAME) == 0);
			}
			FFmpegInvoke.av_frame_free(&frame);
			FFmpegInvoke.av_packet_unref(&packet);
			FFmpegInvoke.avcodec_free_context(&decoder_ctx);
			FFmpegInvoke.avformat_close_input(&input_ctx);
			FFmpegInvoke.av_buffer_unref(&hw_device_ctx);
		}

		private void button1_Click(object sender, EventArgs e)
		{
			var dlg = new OpenFileDialog();
			if (dlg.ShowDialog(this) == DialogResult.OK)
				_path = dlg.FileName;
			tbPath.Text = _path;
		}

		private void button2_Click(object sender, EventArgs e)
		{
			if (_bPlaying)
				backgroundWorker1.CancelAsync();
			else
				backgroundWorker1.RunWorkerAsync();
			button2.Text = _bPlaying ? "Play" : "Pause";
			_bPlaying = !_bPlaying;
		}

		void Play(DoWorkEventArgs e)
		{
			_bInfoCtrlsUpdated = false;
			PlayVideoFile(e);
		}

		private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			Play(e);
		}

		private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			if (!_bInfoCtrlsUpdated)
			{
				tbResolution.Text = _resolution;
				tbTimebase.Text = _timebase;
				tbCodecID.Text = _codecId;
				_bInfoCtrlsUpdated = true;
			}
			progressBar1.Value = e.ProgressPercentage;
		}
	}
}


Что я уже пробовал:

Я пытался LockBits не создавать новый экземпляр Bitmap каждый раз для каждого кадра, но это не сработало. Кроме того, я не знаю, было ли это хорошим решением.

1 Ответов

Рейтинг:
8

Fueled By Caffeine

Вы пытаетесь выполнить операции пользовательского интерфейса(User Interface) в потоке, который не является потоком пользовательского интерфейса.

Либо создайте и отобразите растровое изображение в методе backgroundWorker1_ProgressChanged, либо используйте его.Вызов для выполнения кода в потоке пользовательского интерфейса формы, создания растрового изображения и отображения его в вызываемом методе.

Вы также получаете ширину и высоту pictureBox1 из фонового потока - подумайте о том, чтобы кэшировать их и изменять при изменении размера pictureBox1.