/// <summary> /// Opens the video stream with the specified index in the media container. /// </summary> /// <param name="container">The media container.</param> /// <param name="options">The media options.</param> /// <returns>The opened <see cref="Decoder{TFrame}"/>.</returns> internal static Decoder <VideoFrame> OpenVideo(InputContainer container, MediaOptions options) { var format = container.Pointer; AVCodec *codec = null; var index = ffmpeg.av_find_best_stream(format, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); index.IfError(ffmpeg.AVERROR_DECODER_NOT_FOUND, "Cannot find a codec for the video stream."); if (index < 0) { return(null); } var stream = format->streams[index]; var codecContext = ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(codecContext, stream->codecpar) .ThrowIfError("Cannot open the video codec!"); codecContext->pkt_timebase = stream->time_base; var dict = new FFDictionary(options.DecoderOptions, false).Pointer; ffmpeg.avcodec_open2(codecContext, codec, &dict) .ThrowIfError("Cannot open the video codec"); return(new Decoder <VideoFrame>(codecContext, stream, container)); }
/// <summary> /// Opens the video stream with the specified index in the media container. /// </summary> /// <param name="container">The media container.</param> /// <param name="options">The media options.</param> /// <param name="stream">The stream.</param> /// <returns>The opened <see cref="Decoder"/>.</returns> internal static Decoder OpenStream(InputContainer container, MediaOptions options, AVStream *stream) { var format = container.Pointer; AVCodec *codec = null; var index = ffmpeg.av_find_best_stream(format, stream->codec->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) { return(null); } var codecContext = ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(codecContext, stream->codecpar) .ThrowIfError("Cannot open the stream codec!"); codecContext->pkt_timebase = stream->time_base; var dict = new FFDictionary(options.DecoderOptions, false).Pointer; ffmpeg.avcodec_open2(codecContext, codec, &dict) .ThrowIfError("Cannot open the stream codec!"); return(new Decoder(codecContext, stream, container)); }
/// <summary> /// Opens the video stream with the specified index in the media container. /// </summary> /// <param name="container">The media container.</param> /// <param name="index">The video stream index.</param> /// <param name="options">The media options.</param> /// <returns>The opened <see cref="InputStream{TFrame}"/>.</returns> internal static InputStream <VideoFrame> OpenVideo(InputContainer container, int index, MediaOptions options) { var format = container.Pointer; var stream = format->streams[index]; var codecContext = stream->codec; codecContext->pkt_timebase = stream->time_base; var codec = ffmpeg.avcodec_find_decoder(codecContext->codec_id); if (codec == null) { throw new InvalidOperationException("Cannot find a codec for this stream."); } var dict = new FFDictionary(options.DecoderOptions); var ptr = dict.Pointer; ffmpeg.avcodec_open2(codecContext, codec, &ptr) .ThrowIfError("Cannot open the video codec"); dict.Update(ptr); return(new InputStream <VideoFrame>(stream, container)); }
/// <summary> /// Extracts the chapters from the input. /// </summary> /// <param name="ic">The ic.</param> /// <returns>The chapters.</returns> private static List <ChapterInfo> ExtractChapters(AVFormatContext *ic) { var result = new List <ChapterInfo>(128); if (ic->chapters == null) { return(result); } for (var i = 0; i < ic->nb_chapters; i++) { var c = ic->chapters[i]; var chapter = new ChapterInfo { StartTime = c->start.ToTimeSpan(c->time_base), EndTime = c->end.ToTimeSpan(c->time_base), Index = i, ChapterId = c->id, Metadata = FFDictionary.ToDictionary(c->metadata) }; result.Add(chapter); } return(result); }
/// <summary> /// Initializes a new instance of the <see cref="StreamInfo"/> class. /// </summary> /// <param name="stream">A generic stream.</param> /// <param name="container">The input container.</param> internal unsafe StreamInfo(AVStream *stream, InputContainer container) { var codec = stream->codec; Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(stream->metadata)); CodecName = ffmpeg.avcodec_get_name(codec->codec_id); CodecId = codec->codec_id.FormatEnum(12); Index = stream->index; IsInterlaced = codec->field_order != AVFieldOrder.AV_FIELD_PROGRESSIVE && codec->field_order != AVFieldOrder.AV_FIELD_UNKNOWN; TimeBase = stream->time_base; Duration = stream->duration >= 0 ? stream->duration.ToTimeSpan(stream->time_base) : TimeSpan.FromTicks(container.Pointer->duration * 10); var start = stream->start_time.ToTimeSpan(stream->time_base); StartTime = start == TimeSpan.MinValue ? TimeSpan.Zero : start; if (stream->nb_frames > 0) { IsFrameCountProvidedByContainer = true; FrameCount = (int)stream->nb_frames; } else { FrameCount = Duration.ToFrameNumber(stream->avg_frame_rate); } }
/// <summary> /// Retrieves a dictionary with the options for the specified codec. /// Port of filter_codec_opts /// </summary> /// <param name="codecId">The codec identifier.</param> /// <param name="format">The format.</param> /// <param name="stream">The stream.</param> /// <param name="codec">The codec.</param> /// <returns>The filtered options</returns> internal unsafe FFDictionary FilterOptions(AVCodecID codecId, AVFormatContext *format, AVStream *stream, AVCodec *codec) { var result = new FFDictionary(); if (codec == null) { codec = (format->oformat != null) ? ffmpeg.avcodec_find_encoder(codecId) : ffmpeg.avcodec_find_decoder(codecId); } var codecClass = ffmpeg.avcodec_get_class(); var flags = format->oformat != null ? ffmpeg.AV_OPT_FLAG_ENCODING_PARAM : ffmpeg.AV_OPT_FLAG_DECODING_PARAM; var streamType = (char)0; switch (stream->codecpar->codec_type) { case AVMediaType.AVMEDIA_TYPE_VIDEO: streamType = 'v'; flags |= ffmpeg.AV_OPT_FLAG_VIDEO_PARAM; break; case AVMediaType.AVMEDIA_TYPE_AUDIO: streamType = 'a'; flags |= ffmpeg.AV_OPT_FLAG_AUDIO_PARAM; break; case AVMediaType.AVMEDIA_TYPE_SUBTITLE: streamType = 's'; flags |= ffmpeg.AV_OPT_FLAG_SUBTITLE_PARAM; break; } foreach (var optionItem in Options) { // Inline port of check_stream_specifier var matched = ffmpeg.avformat_match_stream_specifier(format, stream, optionItem.StreamSpecifier.ToString()) > 0; if (matched == false) { continue; } if (ffmpeg.av_opt_find(&codecClass, optionItem.Key, null, flags, ffmpeg.AV_OPT_SEARCH_FAKE_OBJ) != null || codec == null || (codec->priv_class != null && ffmpeg.av_opt_find(&codec->priv_class, optionItem.Key, null, flags, ffmpeg.AV_OPT_SEARCH_FAKE_OBJ) != null)) { result[optionItem.Key] = optionItem.Value; } else if (optionItem.StreamSpecifier.StreamSuffix[0] == streamType && ffmpeg.av_opt_find(&codecClass, optionItem.Key, null, flags, ffmpeg.AV_OPT_SEARCH_FAKE_OBJ) != null) { result[optionItem.Key] = optionItem.Value; } } return(result); }
/// <summary> /// Extracts the stream infos from the input. /// </summary> /// <param name="inputContext">The input context.</param> /// <returns>The list of stream infos.</returns> private static List <StreamInfo> ExtractStreams(AVFormatContext *inputContext) { var result = new List <StreamInfo>(32); if (inputContext->streams == null) { return(result); } for (var i = 0; i < inputContext->nb_streams; i++) { var s = inputContext->streams[i]; var codecContext = ffmpeg.avcodec_alloc_context3(null); #pragma warning disable CS0618 // Type or member is obsolete // ffmpeg.avcodec_parameters_to_context(codecContext, s->codecpar); ffmpeg.avcodec_copy_context(codecContext, s->codec); #pragma warning restore CS0618 // Type or member is obsolete var bitsPerSample = codecContext->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO ? ffmpeg.av_get_bits_per_sample(codecContext->codec_id) : 0; var stream = new StreamInfo { StreamId = s->id, StreamIndex = s->index, Metadata = FFDictionary.ToDictionary(s->metadata), CodecType = codecContext->codec_type, CodecTypeName = ffmpeg.av_get_media_type_string(codecContext->codec_type), Codec = codecContext->codec_id, CodecName = ffmpeg.avcodec_get_name(codecContext->codec_id), CodecProfile = ffmpeg.avcodec_profile_name(codecContext->codec_id, codecContext->profile), ReferenceFrameCount = codecContext->refs, CodecTag = codecContext->codec_tag, PixelFormat = codecContext->pix_fmt, FieldOrder = codecContext->field_order, IsInterlaced = codecContext->field_order != AVFieldOrder.AV_FIELD_PROGRESSIVE && codecContext->field_order != AVFieldOrder.AV_FIELD_UNKNOWN, ColorRange = codecContext->color_range, PixelWidth = codecContext->width, PixelHeight = codecContext->height, HasClosedCaptions = (codecContext->properties & ffmpeg.FF_CODEC_PROPERTY_CLOSED_CAPTIONS) != 0, IsLossless = (codecContext->properties & ffmpeg.FF_CODEC_PROPERTY_LOSSLESS) != 0, Channels = codecContext->channels, BitRate = bitsPerSample > 0 ? bitsPerSample * codecContext->channels * codecContext->sample_rate : codecContext->bit_rate, MaxBitRate = codecContext->rc_max_rate, InfoFrameCount = s->codec_info_nb_frames, TimeBase = s->time_base, SampleFormat = codecContext->sample_fmt, SampleRate = codecContext->sample_rate, DisplayAspectRatio = codecContext->height > 0 ? ffmpeg.av_d2q((double)codecContext->width / codecContext->height, int.MaxValue) :
/// <summary> /// Initializes a new instance of the <see cref="StreamInfo"/> class. /// </summary> /// <param name="stream">The video stream.</param> /// <param name="container">The input container.</param> internal unsafe StreamInfo(AVStream *stream, InputContainer container) { var codec = stream->codec; Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(stream->metadata)); CodecName = ffmpeg.avcodec_get_name(codec->codec_id); CodecId = codec->codec_id.FormatEnum(12); Index = stream->index; 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; TimeBase = stream->time_base; RealFrameRate = stream->r_frame_rate; AvgFrameRate = stream->avg_frame_rate.ToDouble(); IsVariableFrameRate = RealFrameRate.ToDouble() != AvgFrameRate; if (stream->duration >= 0) { Duration = stream->duration.ToTimeSpan(stream->time_base); DurationRaw = stream->duration; } else { Duration = TimeSpan.FromTicks(container.Pointer->duration * 10); DurationRaw = Duration.ToTimestamp(TimeBase); } var start = stream->start_time.ToTimeSpan(stream->time_base); StartTime = start == TimeSpan.MinValue ? TimeSpan.Zero : start; if (stream->nb_frames > 0) { IsFrameCountProvidedByContainer = true; NumberOfFrames = (int)stream->nb_frames; FrameCount = NumberOfFrames.Value; } else { FrameCount = Duration.ToFrameNumber(stream->avg_frame_rate); if (!IsVariableFrameRate) { NumberOfFrames = FrameCount; } else { NumberOfFrames = null; } } }
/// <summary> /// Creates a new audio stream for the specified <see cref="OutputContainer"/>. /// </summary> /// <param name="container">The media container.</param> /// <param name="config">The stream settings.</param> /// <returns>The new audio stream.</returns> public static OutputStream <AudioFrame> CreateAudio(OutputContainer container, AudioEncoderSettings config) { var codecId = config.Codec == AudioCodec.Default ? container.Pointer->oformat->audio_codec : (AVCodecID)config.Codec; if (codecId == AVCodecID.AV_CODEC_ID_NONE) { throw new InvalidOperationException("The media container doesn't support audio!"); } var codec = ffmpeg.avcodec_find_encoder(codecId); if (codec == null) { throw new InvalidOperationException($"Cannot find an encoder with the {codecId}!"); } 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; codecContext->codec_id = codecId; codecContext->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO; 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); if ((container.Pointer->oformat->flags & ffmpeg.AVFMT_GLOBALHEADER) != 0) { codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER; } var dict = new FFDictionary(config.CodecOptions); var ptr = dict.Pointer; ffmpeg.avcodec_open2(codecContext, codec, &ptr); dict.Update(ptr); return(new OutputStream <AudioFrame>(audioStream, container)); }
/// <summary> /// Initializes a new instance of the <see cref="MediaInfo"/> class. /// </summary> /// <param name="container">The input container context.</param> internal unsafe MediaInfo(AVFormatContext *container) { FilePath = new IntPtr(container->url).Utf8ToString(); ContainerFormat = new IntPtr(container->iformat->name).Utf8ToString(); Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(container->metadata)); Bitrate = container->bit_rate > 0 ? container->bit_rate : 0; var timeBase = new AVRational { num = 1, den = ffmpeg.AV_TIME_BASE }; Duration = container->duration != ffmpeg.AV_NOPTS_VALUE ? container->duration.ToTimeSpan(timeBase) : TimeSpan.Zero; StartTime = container->start_time != ffmpeg.AV_NOPTS_VALUE ? container->start_time.ToTimeSpan(timeBase) : TimeSpan.Zero; }
/// <summary> /// Creates a new video stream for the specified <see cref="OutputContainer"/>. /// </summary> /// <param name="container">The media container.</param> /// <param name="config">The stream settings.</param> /// <returns>The new video stream.</returns> public static OutputStream <VideoFrame> CreateVideo(OutputContainer container, VideoEncoderSettings config) { var codecId = config.Codec ?? container.Pointer->oformat->video_codec; if (codecId == AVCodecID.AV_CODEC_ID_NONE) { throw new InvalidOperationException("The media container doesn't support video!"); } var codec = ffmpeg.avcodec_find_encoder(codecId); if (codec == null) { throw new InvalidOperationException($"Cannot find an encoder with the {codecId}!"); } 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); var codecContext = videoStream->codec; codecContext->codec_id = codecId; codecContext->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO; codecContext->bit_rate = config.Bitrate; codecContext->width = config.VideoWidth; codecContext->height = config.VideoHeight; codecContext->time_base.den = config.Framerate; codecContext->time_base.num = 1; 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); var ptr = dict.Pointer; ffmpeg.avcodec_open2(codecContext, codec, &ptr); dict.Update(ptr); return(new OutputStream <VideoFrame>(videoStream, container)); }
/// <summary> /// Initializes a new instance of the <see cref="StreamInfo"/> class. /// </summary> /// <param name="stream">A generic stream.</param> /// <param name="type">The media type of the stream.</param> /// <param name="container">The input container.</param> internal unsafe StreamInfo(AVStream *stream, MediaType type, InputContainer container) { var codec = stream->codec; Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(stream->metadata)); CodecName = ffmpeg.avcodec_get_name(codec->codec_id); CodecId = codec->codec_id.FormatEnum(12); Index = stream->index; Type = type; TimeBase = stream->time_base; RealFrameRate = stream->r_frame_rate; AvgFrameRate = stream->avg_frame_rate.ToDouble(); IsVariableFrameRate = RealFrameRate.ToDouble() != AvgFrameRate; if (stream->duration >= 0) { Duration = stream->duration.ToTimeSpan(stream->time_base); DurationRaw = stream->duration; } else { Duration = TimeSpan.FromTicks(container.Pointer->duration * 10); DurationRaw = Duration.ToTimestamp(TimeBase); } if (stream->start_time >= 0) { StartTime = stream->start_time.ToTimeSpan(stream->time_base); } if (stream->nb_frames > 0) { IsFrameCountProvidedByContainer = true; NumberOfFrames = (int)stream->nb_frames; FrameCount = NumberOfFrames.Value; } else { FrameCount = Duration.ToFrameNumber(stream->avg_frame_rate); if (!IsVariableFrameRate) { NumberOfFrames = FrameCount; } else { NumberOfFrames = null; } } }
private static unsafe StreamChapter[] ParseChapters(InputContainer container) { var streamChapters = new StreamChapter[container.Pointer->nb_chapters]; for (var i = 0; i < container.Pointer->nb_chapters; i++) { var chapter = container.Pointer->chapters[i]; var meta = chapter->metadata; var startTimespan = TimeSpan.FromTicks(chapter->start / 100); var endTimespan = TimeSpan.FromTicks(chapter->end / 100); streamChapters[i] = new StreamChapter(startTimespan, endTimespan, FFDictionary.ToDictionary(meta, true)); } return(streamChapters); }
/// <summary> /// Retrieves an array of dictionaries, one for each stream index /// https://ffmpeg.org/ffplay.html#toc-Options /// Port of setup_find_stream_info_opts. /// </summary> /// <param name="format">The format.</param> /// <returns>The options per stream</returns> internal unsafe FFDictionary[] GetPerStreamOptions(AVFormatContext *format) { if (format->nb_streams == 0) { return(null); } var result = new FFDictionary[format->nb_streams]; for (var i = 0; i < format->nb_streams; i++) { result[i] = FilterOptions(format->streams[i]->codecpar->codec_id, format, format->streams[i], null); } return(result); }
private static unsafe MediaChapter[] ParseChapters(AVFormatContext *container) { var streamChapters = new MediaChapter[container->nb_chapters]; for (var i = 0; i < container->nb_chapters; i++) { var chapter = container->chapters[i]; var meta = chapter->metadata; var startTimespan = chapter->start.ToTimeSpan(chapter->time_base); var endTimespan = chapter->end.ToTimeSpan(chapter->time_base); streamChapters[i] = new MediaChapter(startTimespan, endTimespan, FFDictionary.ToDictionary(meta, true)); } return(streamChapters); }
private static AVFormatContext *MakeContext(string url, MediaOptions options, AVFormatContextDelegate contextDelegate) { FFmpegLoader.LoadFFmpeg(); var context = ffmpeg.avformat_alloc_context(); options.DemuxerOptions.ApplyFlags(context); var dict = new FFDictionary(options.DemuxerOptions.PrivateOptions, false).Pointer; contextDelegate(context); ffmpeg.avformat_open_input(&context, url, null, &dict) .ThrowIfError("An error occurred while opening the file"); ffmpeg.avformat_find_stream_info(context, null) .ThrowIfError("Cannot find stream info"); return(context); }
/// <summary> /// Initializes a new instance of the <see cref="StreamInfo"/> class. /// </summary> /// <param name="stream">The video stram.</param> internal unsafe StreamInfo(AVStream *stream) { var codec = stream->codec; Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(stream->metadata)); CodecName = ffmpeg.avcodec_get_name(codec->codec_id); CodecId = FormatCodecId(codec->codec_id); Index = stream->index; 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; TimeBase = stream->time_base; RFrameRate = stream->r_frame_rate; FrameRate = RFrameRate.ToDouble(); Duration = stream->duration.ToTimeSpan(stream->time_base); var start = stream->start_time.ToTimeSpan(stream->time_base); StartTime = start == TimeSpan.MinValue ? TimeSpan.Zero : start; FrameCount = Duration.ToFrameNumber(RFrameRate); }
/// <summary> /// Opens a media container and stream codecs from given path. /// </summary> /// <param name="path">A path to the multimedia file.</param> /// <param name="options">The media settings.</param> /// <returns>A new instance of the <see cref="InputContainer"/> class.</returns> public static InputContainer LoadFile(string path, MediaOptions options) { MediaToolkit.LoadFFmpeg(); var context = ffmpeg.avformat_alloc_context(); options.DemuxerOptions.ApplyFlags(context); var dict = new FFDictionary(options.DemuxerOptions.PrivateOptions); var ptr = dict.Pointer; ffmpeg.avformat_open_input(&context, path, null, &ptr) .ThrowIfError("An error ocurred while opening the file"); ffmpeg.avformat_find_stream_info(context, null) .ThrowIfError("Cannot find stream info"); dict.Update(ptr); var container = new InputContainer(context); container.OpenStreams(options); return(container); }
/// <summary> /// Extracts the programs from the input and creates associations between programs and streams. /// </summary> /// <param name="ic">The ic.</param> /// <param name="streams">The streams.</param> /// <returns>The program information</returns> private static List <ProgramInfo> ExtractPrograms(AVFormatContext *ic, ReadOnlyDictionary <int, StreamInfo> streams) { var result = new List <ProgramInfo>(); if (ic->programs == null) { return(result); } for (var i = 0; i < ic->nb_programs; i++) { var p = ic->programs[i]; var program = new ProgramInfo { Metadata = new ReadOnlyDictionary <string, string>(FFDictionary.ToDictionary(p->metadata)), ProgramId = p->id, ProgramNumber = p->program_num, }; var associatedStreams = new List <StreamInfo>(); for (var s = 0; s < p->nb_stream_indexes; s++) { var streamIndex = (int)p->stream_index[s]; if (streams.ContainsKey(streamIndex)) { associatedStreams.Add(streams[streamIndex]); } } program.Streams = new ReadOnlyCollection <StreamInfo>(associatedStreams); result.Add(program); } return(result); }
/// <summary> /// Extracts the programs from the input and creates associations between programs and streams. /// </summary> /// <param name="ic">The ic.</param> /// <param name="streams">The streams.</param> /// <returns>The program information.</returns> private static List <ProgramInfo> ExtractPrograms(AVFormatContext *ic, IReadOnlyDictionary <int, StreamInfo> streams) { var result = new List <ProgramInfo>(128); if (ic->programs == null) { return(result); } for (var i = 0; i < ic->nb_programs; i++) { var p = ic->programs[i]; var program = new ProgramInfo { Metadata = FFDictionary.ToDictionary(p->metadata), ProgramId = p->id, ProgramNumber = p->program_num }; var associatedStreams = new List <StreamInfo>(32); for (var s = 0; s < p->nb_stream_indexes; s++) { var streamIndex = Convert.ToInt32(p->stream_index[s]); if (streams.ContainsKey(streamIndex)) { associatedStreams.Add(streams[streamIndex]); } } program.Streams = associatedStreams; result.Add(program); } return(result); }
/// <summary> /// Creates a new video stream for the specified <see cref="OutputContainer"/>. /// </summary> /// <param name="container">The media container.</param> /// <param name="config">The stream settings.</param> /// <returns>The new video stream.</returns> public static OutputStream <VideoFrame> CreateVideo(OutputContainer container, VideoEncoderSettings config) { var codecId = config.Codec == VideoCodec.Default ? container.Pointer->oformat->video_codec : (AVCodecID)config.Codec; if (codecId == AVCodecID.AV_CODEC_ID_NONE) { throw new InvalidOperationException("The media container doesn't support video!"); } var codec = ffmpeg.avcodec_find_encoder(codecId); if (codec == null) { throw new InvalidOperationException($"Cannot find an encoder with the {codecId}!"); } 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; 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); dict.Update(ptr); return(new OutputStream <VideoFrame>(videoStream, container)); }
/// <summary> /// Initializes the input context to start read operations. /// </summary> /// <exception cref="System.InvalidOperationException">The input context has already been initialized.</exception> /// <exception cref="MediaContainerException"></exception> private void StreamInitialize() { if (IsInitialized) { throw new InvalidOperationException("The input context has already been initialized."); } // Retrieve the input format (null = auto for default) AVInputFormat *inputFormat = null; if (string.IsNullOrWhiteSpace(MediaOptions.ForcedInputFormat) == false) { inputFormat = ffmpeg.av_find_input_format(MediaOptions.ForcedInputFormat); Log(MediaLogMessageType.Warning, $"Format '{MediaOptions.ForcedInputFormat}' not found. Will use automatic format detection."); } try { // Create the input format context, and open the input based on the provided format options. using (var formatOptions = new FFDictionary(MediaOptions.FormatOptions)) { if (formatOptions.HasKey(EntryName.ScanAllPMTs) == false) { formatOptions.Set(EntryName.ScanAllPMTs, "1", true); } // Allocate the input context and save it InputContext = ffmpeg.avformat_alloc_context(); // Try to open the input fixed(AVFormatContext **inputContext = &InputContext) { // Open the input file var openResult = 0; fixed(AVDictionary **reference = &formatOptions.Pointer) openResult = ffmpeg.avformat_open_input(inputContext, MediaUrl, inputFormat, reference); // Validate the open operation if (openResult < 0) { throw new MediaContainerException($"Could not open '{MediaUrl}'. Error code: {openResult}"); } } // Set some general properties MediaFormatName = Utils.PtrToString(InputContext->iformat->name); // If there are any optins left in the dictionary, it means they did not get used (invalid options). formatOptions.Remove(EntryName.ScanAllPMTs); var currentEntry = formatOptions.First(); while (currentEntry != null && currentEntry?.Key != null) { Log(MediaLogMessageType.Warning, $"Invalid format option: '{currentEntry.Key}'"); currentEntry = formatOptions.Next(currentEntry); } } // Inject Codec Parameters if (MediaOptions.GeneratePts) { InputContext->flags |= ffmpeg.AVFMT_FLAG_GENPTS; } ffmpeg.av_format_inject_global_side_data(InputContext); // This is useful for file formats with no headers such as MPEG. This function also computes the real framerate in case of MPEG-2 repeat frame mode. if (ffmpeg.avformat_find_stream_info(InputContext, null) < 0) { Log(MediaLogMessageType.Warning, $"{MediaUrl}: could read stream info."); } // TODO: FIXME hack, ffplay maybe should not use avio_feof() to test for the end if (InputContext->pb != null) { InputContext->pb->eof_reached = 0; } // Setup initial state variables { var metadataDictionary = new Dictionary <string, string>(); var metadataEntry = ffmpeg.av_dict_get(InputContext->metadata, "", null, ffmpeg.AV_DICT_IGNORE_SUFFIX); while (metadataEntry != null) { metadataDictionary[Utils.PtrToString(metadataEntry->key)] = Utils.PtrToString(metadataEntry->value); metadataEntry = ffmpeg.av_dict_get(InputContext->metadata, "", metadataEntry, ffmpeg.AV_DICT_IGNORE_SUFFIX); } Metadata = new ReadOnlyDictionary <string, string>(metadataDictionary); } IsStreamRealtime = new[] { "rtp", "rtsp", "sdp" }.Any(s => MediaFormatName.Equals(s)) || (InputContext->pb != null && new[] { "rtp:", "udp:" }.Any(s => MediaUrl.StartsWith(s))); RequiresReadDelay = MediaFormatName.Equals("rstp") || MediaUrl.StartsWith("mmsh:"); var inputAllowsDiscontinuities = (InputContext->iformat->flags & ffmpeg.AVFMT_TS_DISCONT) != 0; MediaSeeksByBytes = inputAllowsDiscontinuities && (MediaFormatName.Equals("ogg") == false); MediaSeeksByBytes = MediaSeeksByBytes && MediaBitrate > 0; // Compute start time and duration (if possible) MediaStartTimeOffset = InputContext->start_time.ToTimeSpan(); if (MediaStartTimeOffset == TimeSpan.MinValue) { Log(MediaLogMessageType.Warning, $"Unable to determine the media start time offset. Media start time offset will be set to zero."); MediaStartTimeOffset = TimeSpan.Zero; } MediaDuration = InputContext->duration.ToTimeSpan(); // Open the best suitable streams. Throw if no audio and/or video streams are found StreamCreateComponents(); // Verify the stream input start offset. This is the zero measure for all sub-streams. var minOffset = Components.All.Count > 0 ? Components.All.Min(c => c.StartTimeOffset) : MediaStartTimeOffset; if (minOffset != MediaStartTimeOffset) { Log(MediaLogMessageType.Warning, $"Input Start: {MediaStartTimeOffset.Debug()} Comp. Start: {minOffset.Debug()}. Input start will be updated."); MediaStartTimeOffset = minOffset; } // For network streams, figure out if reads can be paused and then start them. CanReadSuspend = ffmpeg.av_read_pause(InputContext) == 0; ffmpeg.av_read_play(InputContext); IsReadSuspended = false; // Initially and depending on the video component, rquire picture attachments. // Picture attachments are only required after the first read or after a seek. RequiresPictureAttachments = true; // Seek to the begining of the file StreamSeekToStart(); } catch (Exception ex) { Log(MediaLogMessageType.Error, $"Fatal error initializing {nameof(MediaContainer)} instance. {ex.Message}"); Dispose(true); throw; } }
/// <summary> /// Extracts the stream infos from the input. /// </summary> /// <param name="inputContext">The input context.</param> /// <returns>The list of stream infos.</returns> private static List <StreamInfo> ExtractStreams(AVFormatContext *inputContext) { var result = new List <StreamInfo>(32); if (inputContext->streams == null) { return(result); } for (var i = 0; i < inputContext->nb_streams; i++) { var s = inputContext->streams[i]; var codecContext = ffmpeg.avcodec_alloc_context3(null); ffmpeg.avcodec_parameters_to_context(codecContext, s->codecpar); // Fields which are missing from AVCodecParameters need to be taken // from the stream's AVCodecContext codecContext->properties = s->codec->properties; codecContext->codec = s->codec->codec; codecContext->qmin = s->codec->qmin; codecContext->qmax = s->codec->qmax; codecContext->coded_width = s->codec->coded_height; codecContext->coded_height = s->codec->coded_width; var bitsPerSample = codecContext->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO ? ffmpeg.av_get_bits_per_sample(codecContext->codec_id) : 0; var dar = s->display_aspect_ratio; var sar = s->sample_aspect_ratio; var codecSar = s->codecpar->sample_aspect_ratio; if (sar.num != 0 && (sar.num != codecSar.num || sar.den != codecSar.den)) { ffmpeg.av_reduce( &dar.num, &dar.den, s->codecpar->width * sar.num, s->codecpar->height * sar.den, 1024 * 1024); } var stream = new StreamInfo { StreamId = s->id, StreamIndex = s->index, Metadata = FFDictionary.ToDictionary(s->metadata), CodecType = codecContext->codec_type, CodecTypeName = ffmpeg.av_get_media_type_string(codecContext->codec_type), Codec = codecContext->codec_id, CodecName = ffmpeg.avcodec_get_name(codecContext->codec_id), CodecProfile = ffmpeg.avcodec_profile_name(codecContext->codec_id, codecContext->profile), ReferenceFrameCount = codecContext->refs, CodecTag = codecContext->codec_tag, PixelFormat = codecContext->pix_fmt, FieldOrder = codecContext->field_order, IsInterlaced = codecContext->field_order != AVFieldOrder.AV_FIELD_PROGRESSIVE && codecContext->field_order != AVFieldOrder.AV_FIELD_UNKNOWN, ColorRange = codecContext->color_range, PixelWidth = codecContext->width, PixelHeight = codecContext->height, HasClosedCaptions = (codecContext->properties & ffmpeg.FF_CODEC_PROPERTY_CLOSED_CAPTIONS) != 0, IsLossless = (codecContext->properties & ffmpeg.FF_CODEC_PROPERTY_LOSSLESS) != 0, BitRate = bitsPerSample > 0 ? bitsPerSample * codecContext->channels * codecContext->sample_rate : codecContext->bit_rate, MaxBitRate = codecContext->rc_max_rate, InfoFrameCount = s->codec_info_nb_frames, TimeBase = s->time_base, SampleFormat = codecContext->sample_fmt, SampleRate = codecContext->sample_rate, DisplayAspectRatio = dar, SampleAspectRatio = sar, Disposition = s->disposition, StartTime = s->start_time.ToTimeSpan(s->time_base), Duration = s->duration.ToTimeSpan(s->time_base), FPS = s->avg_frame_rate.ToDouble(), TBR = s->r_frame_rate.ToDouble(), TBN = 1d / s->time_base.ToDouble(), TBC = 1d / s->codec->time_base.ToDouble() }; // Extract valid hardware configurations stream.HardwareDevices = HardwareAccelerator.GetCompatibleDevices(stream.Codec); stream.HardwareDecoders = GetHardwareDecoders(stream.Codec); // TODO: I chose not to include Side data but I could easily do so // https://ffmpeg.org/doxygen/3.2/dump_8c_source.html // See function: dump_sidedata ffmpeg.avcodec_free_context(&codecContext); result.Add(stream); } return(result); }