Skip to content

Commit

Permalink
Update to FFmpeg 5.0 (closes #100, #101)
Browse files Browse the repository at this point in the history
  • Loading branch information
radek-k committed Feb 17, 2022
1 parent 0417ee9 commit 227dd31
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 100 deletions.
4 changes: 2 additions & 2 deletions FFMediaToolkit/Decoding/AudioStreamInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public class AudioStreamInfo : StreamInfo
internal unsafe AudioStreamInfo(AVStream* stream, InputContainer container)
: base(stream, MediaType.Audio, container)
{
var codec = stream->codec;
var codec = stream->codecpar;
NumChannels = codec->channels;
SampleRate = codec->sample_rate;
SamplesPerFrame = codec->frame_size > 0 ? codec->frame_size : codec->sample_rate / 20;
SampleFormat = (SampleFormat)codec->sample_fmt;
SampleFormat = (SampleFormat)codec->format;
ChannelLayout = ffmpeg.av_get_default_channel_layout(codec->channels);
}

Expand Down
2 changes: 1 addition & 1 deletion FFMediaToolkit/Decoding/Internal/DecoderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static Decoder OpenStream(InputContainer container, MediaOptions option
var format = container.Pointer;
AVCodec* codec = null;

var index = ffmpeg.av_find_best_stream(format, stream->codec->codec_type, stream->index, -1, &codec, 0);
var index = ffmpeg.av_find_best_stream(format, stream->codecpar->codec_type, stream->index, -1, &codec, 0);
index.IfError(ffmpeg.AVERROR_DECODER_NOT_FOUND, "Cannot find a codec for the specified stream.");
if (index < 0)
{
Expand Down
20 changes: 10 additions & 10 deletions FFMediaToolkit/Decoding/Internal/InputContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,17 @@ private void OpenStreams(MediaOptions options)
for (int i = 0; i < Pointer->nb_streams; i++)
{
var stream = Pointer->streams[i];
if (!options.ShouldLoadStreamsOfType(stream->codec->codec_type))
continue;

try
{
Decoders[i] = DecoderFactory.OpenStream(this, options, stream);
GetPacketFromStream(i);
}
catch (Exception)
if (options.ShouldLoadStreamsOfType(stream->codecpar->codec_type))
{
Decoders[i] = null;
try
{
Decoders[i] = DecoderFactory.OpenStream(this, options, stream);
GetPacketFromStream(i);
}
catch (Exception)
{
Decoders[i] = null;
}
}
}
}
Expand Down
11 changes: 5 additions & 6 deletions FFMediaToolkit/Decoding/StreamInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public class StreamInfo
/// <param name="container">The input container.</param>
internal unsafe StreamInfo(AVStream* stream, MediaType type, InputContainer container)
{
var codec = stream->codec;
var codecId = stream->codecpar->codec_id;
Metadata = new ReadOnlyDictionary<string, string>(FFDictionary.ToDictionary(stream->metadata));
CodecName = ffmpeg.avcodec_get_name(codec->codec_id);
CodecId = codec->codec_id.FormatEnum(12);
CodecName = ffmpeg.avcodec_get_name(codecId);
CodecId = codecId.FormatEnum(12);
Index = stream->index;
Type = type;

Expand Down Expand Up @@ -157,10 +157,9 @@ internal unsafe StreamInfo(AVStream* stream, MediaType type, InputContainer cont
/// <returns>The resulting new <see cref="StreamInfo"/> object.</returns>
internal static unsafe StreamInfo Create(AVStream* stream, InputContainer owner)
{
var codec = stream->codec;
if (codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
if (stream->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
return new AudioStreamInfo(stream, owner);
if (codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
if (stream->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
return new VideoStreamInfo(stream, owner);
return new StreamInfo(stream, MediaType.None, owner);
}
Expand Down
6 changes: 3 additions & 3 deletions FFMediaToolkit/Decoding/VideoStreamInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ public class VideoStreamInfo : StreamInfo
internal unsafe VideoStreamInfo(AVStream* stream, InputContainer container)
: base(stream, MediaType.Video, container)
{
var codec = stream->codec;
var codec = stream->codecpar;
IsInterlaced = codec->field_order != AVFieldOrder.AV_FIELD_PROGRESSIVE &&
codec->field_order != AVFieldOrder.AV_FIELD_UNKNOWN;
FrameSize = new Size(codec->width, codec->height);
PixelFormat = codec->pix_fmt.FormatEnum(11);
AVPixelFormat = codec->pix_fmt;
PixelFormat = ((AVPixelFormat)codec->format).FormatEnum(11);
AVPixelFormat = (AVPixelFormat)codec->format;
}

/// <summary>
Expand Down
99 changes: 55 additions & 44 deletions FFMediaToolkit/Encoding/Internal/OutputStreamFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,50 +32,54 @@ public static OutputStream<VideoFrame> CreateVideo(OutputContainer container, Vi
if (codec->type != AVMediaType.AVMEDIA_TYPE_VIDEO)
throw new InvalidOperationException($"The {codecId} encoder doesn't support video!");

var videoStream = ffmpeg.avformat_new_stream(container.Pointer, codec);
videoStream->time_base = config.TimeBase;
videoStream->r_frame_rate = config.FramerateRational;

var codecContext = videoStream->codec;
codecContext->codec_id = codecId;
codecContext->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO;

codecContext->width = config.VideoWidth;
codecContext->height = config.VideoHeight;

codecContext->time_base = videoStream->time_base;
codecContext->framerate = videoStream->r_frame_rate;
var stream = ffmpeg.avformat_new_stream(container.Pointer, codec);
if (stream == null)
throw new InvalidOperationException("Cannot allocate AVStream");

stream->time_base = config.TimeBase;
stream->r_frame_rate = config.FramerateRational;

var codecContext = ffmpeg.avcodec_alloc_context3(codec);
if (codecContext == null)
throw new InvalidOperationException("Cannot allocate AVCodecContext");

stream->codecpar->codec_id = codecId;
stream->codecpar->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO;
stream->codecpar->width = config.VideoWidth;
stream->codecpar->height = config.VideoHeight;
stream->codecpar->format = (int)config.VideoFormat;
stream->codecpar->bit_rate = config.Bitrate;

ffmpeg.avcodec_parameters_to_context(codecContext, stream->codecpar).ThrowIfError("Cannot copy stream parameters to encoder");
codecContext->time_base = stream->time_base;
codecContext->framerate = stream->r_frame_rate;
codecContext->gop_size = config.KeyframeRate;
codecContext->pix_fmt = (AVPixelFormat)config.VideoFormat;

if ((container.Pointer->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0)
{
codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER;
}

var dict = new FFDictionary(config.CodecOptions);

if (config.CRF.HasValue && config.Codec.IsMatch(VideoCodec.H264, VideoCodec.H265, VideoCodec.VP9, VideoCodec.VP8))
{
dict["crf"] = config.CRF.Value.ToString();
}
else
{
codecContext->bit_rate = config.Bitrate;
}

if (config.Codec.IsMatch(VideoCodec.H264, VideoCodec.H265))
{
dict["preset"] = config.EncoderPreset.GetDescription();
}

var ptr = dict.Pointer;

ffmpeg.avcodec_open2(codecContext, codec, &ptr);
ffmpeg.avcodec_open2(codecContext, codec, &ptr).ThrowIfError("Failed to open video encoder.");

dict.Update(ptr);

return new OutputStream<VideoFrame>(videoStream, container);
ffmpeg.avcodec_parameters_from_context(stream->codecpar, codecContext).ThrowIfError("Cannot copy encoder parameters to output stream");

if ((container.Pointer->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0)
{
codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER;
}

return new OutputStream<VideoFrame>(stream, codecContext, container);
}

/// <summary>
Expand All @@ -99,34 +103,41 @@ public static OutputStream<AudioFrame> CreateAudio(OutputContainer container, Au
if (codec->type != AVMediaType.AVMEDIA_TYPE_AUDIO)
throw new InvalidOperationException($"The {codecId} encoder doesn't support audio!");

var audioStream = ffmpeg.avformat_new_stream(container.Pointer, codec);
var codecContext = audioStream->codec;

codecContext->time_base = config.TimeBase;
var stream = ffmpeg.avformat_new_stream(container.Pointer, codec);
if (stream == null)
throw new InvalidOperationException("Cannot allocate AVStream");

codecContext->codec_id = codecId;
codecContext->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO;
var codecContext = ffmpeg.avcodec_alloc_context3(codec);
if (codecContext == null)
throw new InvalidOperationException("Cannot allocate AVCodecContext");

codecContext->bit_rate = config.Bitrate;
codecContext->sample_rate = config.SampleRate;
codecContext->frame_size = config.SamplesPerFrame;
codecContext->sample_fmt = (AVSampleFormat)config.SampleFormat;
codecContext->channels = config.Channels;
codecContext->channel_layout = (ulong)ffmpeg.av_get_default_channel_layout(config.Channels);
stream->codecpar->codec_id = codecId;
stream->codecpar->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO;
stream->codecpar->sample_rate = config.SampleRate;
stream->codecpar->frame_size = config.SamplesPerFrame;
stream->codecpar->format = (int)config.SampleFormat;
stream->codecpar->channels = config.Channels;
stream->codecpar->channel_layout = (ulong)ffmpeg.av_get_default_channel_layout(config.Channels);
stream->codecpar->bit_rate = config.Bitrate;

if ((container.Pointer->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0)
{
codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER;
}
ffmpeg.avcodec_parameters_to_context(codecContext, stream->codecpar).ThrowIfError("Cannot copy stream parameters to encoder");
codecContext->time_base = config.TimeBase;

var dict = new FFDictionary(config.CodecOptions);
var ptr = dict.Pointer;

ffmpeg.avcodec_open2(codecContext, codec, &ptr);
ffmpeg.avcodec_open2(codecContext, codec, &ptr).ThrowIfError("Failed to open audio encoder.");

dict.Update(ptr);

return new OutputStream<AudioFrame>(audioStream, container);
ffmpeg.avcodec_parameters_from_context(stream->codecpar, codecContext).ThrowIfError("Cannot copy encoder parameters to output stream");

if ((container.Pointer->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0)
{
codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER;
}

return new OutputStream<AudioFrame>(stream, codecContext, container);
}
}
}
32 changes: 13 additions & 19 deletions FFMediaToolkit/Encoding/Internal/OutputStream{TFrame}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ internal unsafe class OutputStream<TFrame> : Wrapper<AVStream>
where TFrame : MediaFrame
{
private readonly MediaPacket packet;
private AVCodecContext* codecContext;

/// <summary>
/// Initializes a new instance of the <see cref="OutputStream{TFrame}"/> class.
/// </summary>
/// <param name="stream">The multimedia stream.</param>
/// <param name="codec">Codec context.</param>
/// <param name="owner">The container that owns the stream.</param>
public OutputStream(AVStream* stream, OutputContainer owner)
public OutputStream(AVStream* stream, AVCodecContext* codec, OutputContainer owner)
: base(stream)
{
OwnerFile = owner;
codecContext = codec;
packet = MediaPacket.AllocateEmpty();
}

Expand All @@ -31,11 +34,6 @@ public OutputStream(AVStream* stream, OutputContainer owner)
/// </summary>
public OutputContainer OwnerFile { get; }

/// <summary>
/// Gets a pointer to <see cref="AVCodecContext"/> for this stream.
/// </summary>
public AVCodecContext* CodecPointer => Pointer->codec;

/// <summary>
/// Gets the stream index.
/// </summary>
Expand All @@ -52,19 +50,14 @@ public OutputStream(AVStream* stream, OutputContainer owner)
/// <param name="frame">The media frame.</param>
public void Push(TFrame frame)
{
ffmpeg.avcodec_send_frame(CodecPointer, frame.Pointer)
ffmpeg.avcodec_send_frame(codecContext, frame.Pointer)
.ThrowIfError("Cannot send a frame to the encoder.");

if (ffmpeg.avcodec_receive_packet(CodecPointer, packet) == 0)
if (ffmpeg.avcodec_receive_packet(codecContext, packet) == 0)
{
packet.RescaleTimestamp(CodecPointer->time_base, TimeBase);
packet.RescaleTimestamp(codecContext->time_base, TimeBase);
packet.StreamIndex = Index;

if (CodecPointer->coded_frame->key_frame == 1)
{
packet.IsKeyPacket = true;
}

OwnerFile.WritePacket(packet);
}

Expand All @@ -77,19 +70,20 @@ protected override void OnDisposing()
FlushEncoder();
packet.Dispose();

var ptr = CodecPointer;
ffmpeg.avcodec_close(ptr);
ffmpeg.avcodec_close(codecContext);
ffmpeg.av_free(codecContext);
codecContext = null;
}

private void FlushEncoder()
{
while (true)
{
ffmpeg.avcodec_send_frame(CodecPointer, null);
ffmpeg.avcodec_send_frame(codecContext, null);

if (ffmpeg.avcodec_receive_packet(CodecPointer, packet) == 0)
if (ffmpeg.avcodec_receive_packet(codecContext, packet) == 0)
{
packet.RescaleTimestamp(CodecPointer->time_base, TimeBase);
packet.RescaleTimestamp(codecContext->time_base, TimeBase);
packet.StreamIndex = Index;
OwnerFile.WritePacket(packet);
}
Expand Down
10 changes: 2 additions & 8 deletions FFMediaToolkit/Encoding/VideoOutputStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ internal VideoOutputStream(OutputStream<VideoFrame> stream, VideoEncoderSettings
Configuration = config;
converter = new ImageConverter();

var (size, format) = GetStreamLayout(stream);
encodedFrame = VideoFrame.Create(size, format);
var frameSize = new Size(config.VideoWidth, config.VideoHeight);
encodedFrame = VideoFrame.Create(frameSize, (AVPixelFormat)config.VideoFormat);
}

/// <summary>
Expand Down Expand Up @@ -89,11 +89,5 @@ public void Dispose()

isDisposed = true;
}

private static unsafe (Size, AVPixelFormat) GetStreamLayout(OutputStream<VideoFrame> videoStream)
{
var codec = videoStream.Pointer->codec;
return (new Size(codec->width, codec->height), codec->pix_fmt);
}
}
}
9 changes: 4 additions & 5 deletions FFMediaToolkit/FFMediaToolkit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Radosław Kmiotek</Authors>
<Company>radek-k</Company>
<Copyright>Copyright (c) 2019-2021 Radosław Kmiotek</Copyright>
<Copyright>Copyright (c) 2019-2022 Radosław Kmiotek</Copyright>
<Description>Cross-platform audio/video processing library based on FFmpeg native libraries. Supports audio/video frames extraction (fast access to any frame by timestamp), reading file metadata and encoding media files from bitmap images and audio data.</Description>
<PackageTags>ffmpeg;video;audio;encoder;encoding;decoder;decoding;h264;mp4;c#;netstandard;netcore;frame-extraction</PackageTags>
<VersionPrefix>4.1.2</VersionPrefix>
<VersionPrefix>4.2.0</VersionPrefix>
<RepositoryUrl>https://github.com/radek-k/FFMediaToolkit</RepositoryUrl>
<PackageProjectUrl>https://github.com/radek-k/FFMediaToolkit</PackageProjectUrl>
<PackageLicenseFile></PackageLicenseFile>
Expand All @@ -30,13 +30,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FFmpeg.AutoGen" Version="4.4.1" />
<PackageReference Include="FFmpeg.AutoGen" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.StartsWith('netstandard2.0'))">
Expand Down
4 changes: 2 additions & 2 deletions FFMediaToolkit/FFmpegLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static void LoadFFmpeg()
catch (DirectoryNotFoundException)
{
throw new DirectoryNotFoundException("Cannot found the default FFmpeg directory.\n" +
"On Windows you have to set \"FFmpegLoader.FFmpegPath\" with full path to the directory containing FFmpeg shared build \".dll\" files\n" +
"On Windows you have to set \"FFmpegLoader.FFmpegPath\" with full path to the directory containing FFmpeg 5.x shared build \".dll\" files\n" +
"For more informations please see https://github.com/radek-k/FFMediaToolkit#setup");
}
}
Expand Down Expand Up @@ -167,7 +167,7 @@ public static unsafe void SetupLogging()
/// <param name="exception">The original exception.</param>
internal static void HandleLibraryLoadError(Exception exception)
{
throw new DllNotFoundException($"Cannot load required FFmpeg libraries from {FFmpegPath} directory.\nMake sure the \"Build\"Prefer 32-bit\" option in the project settings is turned off.\nFor more informations please see https://github.com/radek-k/FFMediaToolkit#setup", exception);
throw new DllNotFoundException($"Cannot load FFmpeg libraries from {FFmpegPath} directory.\nRequired FFmpeg version: 5.x (shared build)\nMake sure the \"Build\"Prefer 32-bit\" option in the project settings is turned off.\nFor more information please see https://github.com/radek-k/FFMediaToolkit#setup", exception);
}
}
}

0 comments on commit 227dd31

Please sign in to comment.