/// <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));
        }
Beispiel #2
0
        /// <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));
        }
Beispiel #3
0
        /// <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));
        }
Beispiel #4
0
        /// <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);
        }
Beispiel #5
0
        /// <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);
            }
        }
Beispiel #6
0
        /// <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);
        }
Beispiel #7
0
        /// <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) :
Beispiel #8
0
        /// <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));
        }
Beispiel #10
0
        /// <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));
        }
Beispiel #12
0
        /// <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;
                }
            }
        }
Beispiel #13
0
        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);
        }
Beispiel #14
0
        /// <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);
        }
Beispiel #15
0
        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);
        }
Beispiel #16
0
        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);
        }
Beispiel #19
0
        /// <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);
        }
Beispiel #20
0
        /// <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));
        }
Beispiel #22
0
        /// <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;
            }
        }
Beispiel #23
0
        /// <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);
        }