/// <summary> /// Removes the component of specified media type (if registered). /// It calls the dispose method of the media component too. /// </summary> /// <param name="mediaType">Type of the media.</param> internal void RemoveComponent(MediaType mediaType) { lock (ComponentSyncLock) { var component = default(MediaComponent); switch (mediaType) { case MediaType.Audio: component = m_Audio; m_Audio = null; break; case MediaType.Video: component = m_Video; m_Video = null; break; case MediaType.Subtitle: component = m_Subtitle; m_Subtitle = null; break; default: break; } component?.Dispose(); UpdateComponentBackingFields(); } }
/// <summary> /// Removes the component of specified media type (if registered). /// It calls the dispose method of the media component too. /// </summary> /// <param name="mediaType">Type of the media.</param> internal void RemoveComponent(MediaType mediaType) { lock (ComponentSyncLock) { var component = default(MediaComponent); if (mediaType == MediaType.Audio) { component = m_Audio; m_Audio = null; } else if (mediaType == MediaType.Video) { component = m_Video; m_Video = null; } else if (mediaType == MediaType.Subtitle) { component = m_Subtitle; m_Subtitle = null; } component?.Dispose(); UpdateComponentBackingFields(); } }
/// <summary> /// Initializes a new instance of the <see cref="VideoFrame" /> class. /// </summary> /// <param name="frame">The frame.</param> /// <param name="component">The video component.</param> internal VideoFrame(AVFrame *frame, VideoComponent component) : base(frame, component, MediaType.Video) { var timeBase = ffmpeg.av_guess_frame_rate(component.Container.InputContext, component.Stream, frame); var mainOffset = component.Container.Components.Main.StartTime; var repeatFactor = 1d + (0.5d * frame->repeat_pict); Duration = frame->pkt_duration <= 0 ? repeatFactor.ToTimeSpan(new AVRational { num = timeBase.den, den = timeBase.num }) : frame->pkt_duration.ToTimeSpan(component.Stream->time_base); // for video frames, we always get the best effort timestamp as dts and pts might // contain different times. frame->pts = frame->best_effort_timestamp; HasValidStartTime = frame->pts != ffmpeg.AV_NOPTS_VALUE; StartTime = frame->pts == ffmpeg.AV_NOPTS_VALUE ? TimeSpan.FromTicks(0) : TimeSpan.FromTicks(frame->pts.ToTimeSpan(StreamTimeBase).Ticks - mainOffset.Ticks); EndTime = TimeSpan.FromTicks(StartTime.Ticks + Duration.Ticks); // Picture Type, Number and SMTPE TimeCode PictureType = frame->pict_type; DisplayPictureNumber = frame->display_picture_number == 0 ? Extensions.ComputePictureNumber(StartTime, Duration, 1) : frame->display_picture_number; CodedPictureNumber = frame->coded_picture_number; SmtpeTimeCode = Extensions.ComputeSmtpeTimeCode(component.StartTime, Duration, timeBase, DisplayPictureNumber); IsHardwareFrame = component.IsUsingHardwareDecoding; HardwareAcceleratorName = component.HardwareAccelerator?.Name; // Process side data such as CC packets for (var i = 0; i < frame->nb_side_data; i++) { var sideData = frame->side_data[i]; // Get the Closed-Caption packets if (sideData->type != AVFrameSideDataType.AV_FRAME_DATA_A53_CC) { continue; } // Parse 3 bytes at a time for (var p = 0; p < sideData->size; p += 3) { var packet = new ClosedCaptionPacket(TimeSpan.FromTicks(StartTime.Ticks + p), sideData->data, p); if (packet.PacketType == CaptionsPacketType.NullPad || packet.PacketType == CaptionsPacketType.Unrecognized) { continue; } // at this point, we have valid CC data ClosedCaptions.Add(packet); } } }
/// <summary> /// Initializes a new instance of the <see cref="HardwareAccelerator"/> class. /// </summary> /// <param name="component">The component this accelerator is attached to.</param> /// <param name="selectedConfig">The selected hardware device configuration.</param> public HardwareAccelerator(VideoComponent component, HardwareDeviceInfo selectedConfig) { Component = component; Name = selectedConfig.DeviceTypeName; DeviceType = selectedConfig.DeviceType; PixelFormat = selectedConfig.PixelFormat; GetFormatCallback = new AVCodecContext_get_format(GetPixelFormat); }
/// <summary> /// Initializes a new instance of the <see cref="HardwareAccelerator"/> class. /// </summary> /// <param name="component">The component this accelerator is attached to.</param> /// <param name="selectedConfig">The selected hardware device configuration.</param> public HardwareAccelerator(VideoComponent component, HardwareDeviceInfo selectedConfig) { Component = component; Name = selectedConfig.DeviceTypeName; DeviceType = selectedConfig.DeviceType; PixelFormat = selectedConfig.PixelFormat; GetFormatCallback = GetPixelFormat; }
/// <summary> /// Registers the component in this component set. /// </summary> /// <param name="component">The component.</param> /// <exception cref="ArgumentNullException">When component of the same type is already registered</exception> /// <exception cref="NotSupportedException">When MediaType is not supported</exception> /// <exception cref="ArgumentException">When the component is null</exception> internal void AddComponent(MediaComponent component) { lock (ComponentSyncLock) { if (component == null) { throw new ArgumentNullException(nameof(component)); } var errorMessage = $"A component for '{component.MediaType}' is already registered."; switch (component.MediaType) { case MediaType.Audio: if (m_Audio != null) { throw new ArgumentException(errorMessage); } m_Audio = component as AudioComponent; break; case MediaType.Video: if (m_Video != null) { throw new ArgumentException(errorMessage); } m_Video = component as VideoComponent; break; case MediaType.Subtitle: if (m_Subtitle != null) { throw new ArgumentException(errorMessage); } m_Subtitle = component as SubtitleComponent; break; default: throw new NotSupportedException($"Unable to register component with {nameof(MediaType)} '{component.MediaType}'"); } UpdateComponentBackingFields(); } }
/// <summary> /// Attaches a hardware device context to the specified video component. /// </summary> /// <param name="component">The component.</param> /// <exception cref="Exception">Throws when unable to initialize the hardware device</exception> public void AttachDevice(VideoComponent component) { var result = 0; fixed(AVBufferRef **devContextRef = &component.HardwareDeviceContext) { result = ffmpeg.av_hwdevice_ctx_create(devContextRef, DeviceType, null, null, 0); if (result < 0) { throw new Exception($"Unable to initialize hardware context for device {Name}"); } } component.HardwareAccelerator = this; component.CodecContext->hw_device_ctx = ffmpeg.av_buffer_ref(component.HardwareDeviceContext); component.CodecContext->get_format = GetFormatCallback; }
/// <summary> /// Detaches and disposes the hardware device context from the specified video component /// </summary> /// <param name="component">The component.</param> public void DetachDevice(VideoComponent component) { // TODO: (Floyd) Check the below code in the future because I am not sure // how to uninitialize the hardware device context if (component.CodecContext != null) { ffmpeg.av_buffer_unref(&component.CodecContext->hw_device_ctx); component.CodecContext->hw_device_ctx = null; } if (component.HardwareDeviceContext != null) { fixed(AVBufferRef **hwdc = &component.HardwareDeviceContext) { ffmpeg.av_buffer_unref(hwdc); component.HardwareDeviceContext = null; component.HardwareAccelerator = null; } } }
/// <summary> /// Attaches a hardware accelerator to the specified component. /// </summary> /// <param name="component">The component.</param> /// <param name="selectedConfig">The selected configuration.</param> /// <returns> /// Whether or not the hardware accelerator was attached /// </returns> public static bool Attach(VideoComponent component, HardwareDeviceInfo selectedConfig) { try { var result = new HardwareAccelerator { Component = component, Name = selectedConfig.DeviceTypeName, DeviceType = selectedConfig.DeviceType, PixelFormat = selectedConfig.PixelFormat, }; result.InitializeHardwareContext(); return(true); } catch (Exception ex) { component.Container.Parent?.Log(MediaLogMessageType.Error, $"Could not attach hardware decoder. {ex.Message}"); return(false); } }