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
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|