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.

92 lines
3.6 KiB

3 years ago
using FFmpeg.AutoGen;
using System;
using System.Drawing;
using System.IO;
namespace EC.FFmpegAutoGen
{
public sealed unsafe class H264VideoStreamEncoder : IDisposable
{
private readonly Size _frameSize;
private readonly int _linesizeU;
private readonly int _linesizeV;
private readonly int _linesizeY;
private readonly AVCodec* _pCodec;
private readonly AVCodecContext* _pCodecContext;
private readonly Stream _stream;
private readonly int _uSize;
private readonly int _ySize;
public H264VideoStreamEncoder(Stream stream, int fps, Size frameSize)
{
_stream = stream;
_frameSize = frameSize;
var codecId = AVCodecID.AV_CODEC_ID_H264;
_pCodec = ffmpeg.avcodec_find_encoder(codecId);
if (_pCodec == null) throw new InvalidOperationException("Codec not found.");
_pCodecContext = ffmpeg.avcodec_alloc_context3(_pCodec);
_pCodecContext->width = frameSize.Width;
_pCodecContext->height = frameSize.Height;
_pCodecContext->time_base = new AVRational { num = 1, den = fps };
_pCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryslow", 0);
ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null).ThrowExceptionIfError();
_linesizeY = frameSize.Width;
_linesizeU = frameSize.Width / 2;
_linesizeV = frameSize.Width / 2;
_ySize = _linesizeY * frameSize.Height;
_uSize = _linesizeU * frameSize.Height / 2;
}
public void Dispose()
{
ffmpeg.avcodec_close(_pCodecContext);
ffmpeg.av_free(_pCodecContext);
ffmpeg.av_free(_pCodec);
}
public void Encode(AVFrame frame)
{
if (frame.format != (int) _pCodecContext->pix_fmt)
throw new ArgumentException("Invalid pixel format.", nameof(frame));
if (frame.width != _frameSize.Width) throw new ArgumentException("Invalid width.", nameof(frame));
if (frame.height != _frameSize.Height) throw new ArgumentException("Invalid height.", nameof(frame));
if (frame.linesize[0] < _linesizeY) throw new ArgumentException("Invalid Y linesize.", nameof(frame));
if (frame.linesize[1] < _linesizeU) throw new ArgumentException("Invalid U linesize.", nameof(frame));
if (frame.linesize[2] < _linesizeV) throw new ArgumentException("Invalid V linesize.", nameof(frame));
if (frame.data[1] - frame.data[0] < _ySize)
throw new ArgumentException("Invalid Y data size.", nameof(frame));
if (frame.data[2] - frame.data[1] < _uSize)
throw new ArgumentException("Invalid U data size.", nameof(frame));
var pPacket = ffmpeg.av_packet_alloc();
try
{
int error;
do
{
ffmpeg.avcodec_send_frame(_pCodecContext, &frame).ThrowExceptionIfError();
ffmpeg.av_packet_unref(pPacket);
error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket);
} while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
error.ThrowExceptionIfError();
using var packetStream = new UnmanagedMemoryStream(pPacket->data, pPacket->size);
packetStream.CopyTo(_stream);
}
finally
{
ffmpeg.av_packet_free(&pPacket);
}
}
}
}