You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
4.7 KiB
131 lines
4.7 KiB
using FFmpeg.AutoGen;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace EC.FFmpegAutoGen
|
|
{
|
|
public sealed unsafe class VideoStreamDecoder : IDisposable
|
|
{
|
|
private readonly AVCodecContext* _pCodecContext;
|
|
private readonly AVFormatContext* _pFormatContext;
|
|
private readonly AVFrame* _pFrame;
|
|
private readonly AVPacket* _pPacket;
|
|
private readonly AVFrame* _receivedFrame;
|
|
private readonly int _streamIndex;
|
|
|
|
public VideoStreamDecoder(string url, AVHWDeviceType HWDeviceType = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
|
|
{
|
|
_pFormatContext = ffmpeg.avformat_alloc_context();
|
|
_receivedFrame = ffmpeg.av_frame_alloc();
|
|
var pFormatContext = _pFormatContext;
|
|
ffmpeg.avformat_open_input(&pFormatContext, url, null, null).ThrowExceptionIfError();
|
|
ffmpeg.avformat_find_stream_info(_pFormatContext, null).ThrowExceptionIfError();
|
|
AVCodec* codec = null;
|
|
_streamIndex = ffmpeg
|
|
.av_find_best_stream(_pFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)
|
|
.ThrowExceptionIfError();
|
|
_pCodecContext = ffmpeg.avcodec_alloc_context3(codec);
|
|
if (HWDeviceType != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
|
|
ffmpeg.av_hwdevice_ctx_create(&_pCodecContext->hw_device_ctx, HWDeviceType, null, null, 0)
|
|
.ThrowExceptionIfError();
|
|
ffmpeg.avcodec_parameters_to_context(_pCodecContext, _pFormatContext->streams[_streamIndex]->codecpar)
|
|
.ThrowExceptionIfError();
|
|
ffmpeg.avcodec_open2(_pCodecContext, codec, null).ThrowExceptionIfError();
|
|
|
|
CodecName = ffmpeg.avcodec_get_name(codec->id);
|
|
FrameSize = new Size(_pCodecContext->width, _pCodecContext->height);
|
|
PixelFormat = _pCodecContext->pix_fmt;
|
|
|
|
_pPacket = ffmpeg.av_packet_alloc();
|
|
_pFrame = ffmpeg.av_frame_alloc();
|
|
}
|
|
|
|
public string CodecName { get; }
|
|
public Size FrameSize { get; }
|
|
public AVPixelFormat PixelFormat { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
var pFrame = _pFrame;
|
|
ffmpeg.av_frame_free(&pFrame);
|
|
|
|
var pPacket = _pPacket;
|
|
ffmpeg.av_packet_free(&pPacket);
|
|
|
|
ffmpeg.avcodec_close(_pCodecContext);
|
|
var pFormatContext = _pFormatContext;
|
|
ffmpeg.avformat_close_input(&pFormatContext);
|
|
}catch(Exception ex)
|
|
{
|
|
Console.WriteLine(ex);
|
|
}
|
|
|
|
}
|
|
|
|
public bool TryDecodeNextFrame(out AVFrame frame)
|
|
{
|
|
ffmpeg.av_frame_unref(_pFrame);
|
|
ffmpeg.av_frame_unref(_receivedFrame);
|
|
int error;
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
do
|
|
{
|
|
ffmpeg.av_packet_unref(_pPacket);
|
|
error = ffmpeg.av_read_frame(_pFormatContext, _pPacket);
|
|
|
|
if (error == ffmpeg.AVERROR_EOF)
|
|
{
|
|
frame = *_pFrame;
|
|
return false;
|
|
}
|
|
|
|
error.ThrowExceptionIfError();
|
|
} while (_pPacket->stream_index != _streamIndex);
|
|
|
|
ffmpeg.avcodec_send_packet(_pCodecContext, _pPacket).ThrowExceptionIfError();
|
|
}
|
|
finally
|
|
{
|
|
ffmpeg.av_packet_unref(_pPacket);
|
|
}
|
|
|
|
error = ffmpeg.avcodec_receive_frame(_pCodecContext, _pFrame);
|
|
} while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
|
|
|
|
error.ThrowExceptionIfError();
|
|
|
|
if (_pCodecContext->hw_device_ctx != null)
|
|
{
|
|
ffmpeg.av_hwframe_transfer_data(_receivedFrame, _pFrame, 0).ThrowExceptionIfError();
|
|
frame = *_receivedFrame;
|
|
}
|
|
else
|
|
frame = *_pFrame;
|
|
|
|
return true;
|
|
}
|
|
|
|
public IReadOnlyDictionary<string, string> GetContextInfo()
|
|
{
|
|
AVDictionaryEntry* tag = null;
|
|
var result = new Dictionary<string, string>();
|
|
|
|
while ((tag = ffmpeg.av_dict_get(_pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
|
|
{
|
|
var key = Marshal.PtrToStringAnsi((IntPtr) tag->key);
|
|
var value = Marshal.PtrToStringAnsi((IntPtr) tag->value);
|
|
result.Add(key, value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|