Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New version of Ffmpeg.AutoGen has some break changes. #95

Open
zydjohnHotmail opened this issue Apr 18, 2024 · 0 comments
Open

New version of Ffmpeg.AutoGen has some break changes. #95

zydjohnHotmail opened this issue Apr 18, 2024 · 0 comments

Comments

@zydjohnHotmail
Copy link

I show you the current test code with a WinForms project:
using HLSDownload.Converter;
using HLSDownload.Grabber;
using HLSDownload.Grabber.Grabbed;
using HLSDownload.HLS;
using System.Diagnostics;

namespace HLSDownloadTestForm
{
public partial class Form1 : Form
{
private const string ffmpeg_path =
@"C:\Program Files\ffmpeg\ffmpeg.exe";
private const string output_path =
@"G:\VideoFiles";
private const string m3u8_url1 =
@"https://content.jwplatform.com/manifests/yp34SRmf.m3u8";
private static readonly IGrabber _grabber;
private static readonly HttpClient _client =
new();

    public Form1()
    {
        InitializeComponent();
    }

    static Form1()
    {
        IMultiGrabber grabber = GrabberBuilder.New()
          .UseDefaultServices()
          .AddHls()
          .Build();
        _grabber = grabber;
    }

    public static void DeleteTSFiles()
    {
        string directoryPath = Path.GetTempPath();
        IEnumerable<string> tsFiles = 
            Directory.EnumerateFiles(directoryPath, "*.ts");
        foreach (string file in tsFiles)
        {
            try
            {
                File.Delete(file);
            }
            catch (Exception ex)
            {
                Debug.Print($"Unable to delete file {file}. Error: {ex.Message}");
                continue;
            }
        }
    }

    private static async Task Grab(Uri uri)
    {
        Debug.Print($"Grabbing from {uri}...");
        GrabResult grabResult = 
            await _grabber.GrabAsync(uri).ConfigureAwait(false);
        GrabbedHlsStreamReference reference = 
            grabResult.Resource<GrabbedHlsStreamReference>();
        if (grabResult == null)
        {
            Debug.Print($"GrabResult is null for URI: {uri}");
            return;
        }
        if (reference != null)
        {
            // Redirect to an M3U8 playlist
            await Grab(reference.ResourceUri);
            return;
        }
        GrabbedHlsStreamMetadata[] metadataResources = 
            grabResult.Resources<GrabbedHlsStreamMetadata>().ToArray();
        if (metadataResources.Length > 0)
        {
            // Description for one or more M3U8 playlists
            GrabbedHlsStreamMetadata selection;
            if (metadataResources.Length == 1)
            {
                selection = metadataResources.Single();
            }
            else
            {
                Debug.Print("");
                Debug.Print("=== Streams ===");
                selection = metadataResources[int.Parse("0")];
            }
            // Get information from the HLS stream
            GrabbedHlsStream grabbedStream = await selection.Stream.Value;
            await Grab(grabbedStream, selection, grabResult);
            return;
        }
        throw new Exception("Could not grab the HLS stream.");
    }

    private static async Task Grab(GrabbedHlsStream stream, 
    GrabbedHlsStreamMetadata metadata, GrabResult grabResult)
    {
        Debug.Print("");
        Debug.Print("=== Downloading ===");
        Debug.Print("{0} segments", stream.Segments.Count);
        Debug.Print("Duration: {0}", stream.Length);
        List<string> tempFiles = [];
        try
        {
            for (int i = 0; i < stream.Segments.Count; i++)
            {
                MediaSegment segment = stream.Segments[i];
                Debug.Write($"Downloading segment #{i + 1} {segment.Title}...");
                string outputPath = Path.GetTempFileName();
                tempFiles.Add(outputPath);
                using Stream responseStream = 
                    await _client.GetStreamAsync(segment.Uri);
                using Stream inputStream = 
                    await grabResult.WrapStreamAsync(responseStream);
                using FileStream outputStream = new(outputPath, FileMode.Create);
                await inputStream.CopyToAsync(outputStream);
                Debug.Print(" OK");
            }
            CreateOutputFile(tempFiles, metadata);
        }
        finally
        {
            foreach (string tempFile in tempFiles)
            {
                if (File.Exists(tempFile))
                    File.Delete(tempFile);
            }
            Debug.Print("Cleaned up temp files.");
        }
    }

    private static void CreateOutputFile(List<string> tempFiles, GrabbedHlsStreamMetadata metadata)
    {
        Debug.Print("All segments were downloaded successfully.");
        MediaConcatenator concatenator = new(output_path)
        {
            OutputMimeType = metadata.OutputFormat.Mime,
            OutputExtension = metadata.OutputFormat.Extension,
        };
        foreach (string tempFile in tempFiles)
            concatenator.AddSource(tempFile);
        concatenator.Build();
        Debug.Print("Output file created successfully!");
    }

    private async void BTNDownload_Click(object sender, EventArgs e)
    {
        DeleteTSFiles();
        FFmpeg.AutoGen.ffmpeg.RootPath = ffmpeg_path;
        await Grab(new Uri(m3u8_url1));
    }

    private void BTNFinish_Click(object sender, EventArgs e)
    {
        Console.Beep();
        Environment.Exit(0);
    }
}

}
=>
I show you the debug output from this WinForms test code:
OK, I changed the class library, and it got compiled, but when I run the test code, I got the same error:
System.Reflection.TargetInvocationException
HResult=0x80131604
Message=Exception has been thrown by the target of an invocation.
Source=System.Private.CoreLib
StackTrace:
at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in System.Reflection\MethodBaseInvoker.cs:line 117
at System.Delegate.DynamicInvokeImpl(Object[] args) in System\Delegate.cs:line 55
at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in System.Threading\ExecutionContext.cs:line 154
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in System.Threading\ExecutionContext.cs:line 154
at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(HWND hWnd, MessageId msg, WPARAM wparam, LPARAM lparam)
at Windows.Win32.PInvoke.DispatchMessage(MSG* lpMsg)
at System.Windows.Forms.Application.ComponentManager.Microsoft.Office.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context)
at HLSDownloadTestForm.Program.Main() in D:\RaceOCR\HLSVideo\HLSDownloadTestForm\HLSDownloadTestForm\Program.cs:line 14

This exception was originally thrown at this call stack:
FFmpeg.AutoGen.DynamicallyLoadedBindings.Initialize.AnonymousMethod__2_1320(FFmpeg.AutoGen.AVIOContext**, string, int, FFmpeg.AutoGen.AVIOInterruptCB*, FFmpeg.AutoGen.AVDictionary**)
FFmpeg.AutoGen.DynamicallyLoadedBindings.Initialize.AnonymousMethod__2_609(FFmpeg.AutoGen.AVIOContext**, string, int, FFmpeg.AutoGen.AVIOInterruptCB*, FFmpeg.AutoGen.AVDictionary**)
FFmpeg.AutoGen.ffmpeg.avio_open2(FFmpeg.AutoGen.AVIOContext**, string, int, FFmpeg.AutoGen.AVIOInterruptCB*, FFmpeg.AutoGen.AVDictionary**)
HLSDownload.Converter.IOContext.IOContext(string, int) in IOContext.cs
HLSDownload.Converter.MediaConcatenator.Build() in MediaConcatenator.cs
HLSDownloadTestForm.Form1.CreateOutputFile(System.Collections.Generic.List, HLSDownload.Grabber.Grabbed.GrabbedHlsStreamMetadata) in Form1.cs
HLSDownloadTestForm.Form1.Grab(HLSDownload.Grabber.Grabbed.GrabbedHlsStream, HLSDownload.Grabber.Grabbed.GrabbedHlsStreamMetadata, HLSDownload.Grabber.GrabResult) in Form1.cs
HLSDownloadTestForm.Form1.Grab(System.Uri) in Form1.cs
HLSDownloadTestForm.Form1.BTNDownload_Click(object, System.EventArgs) in Form1.cs
System.Threading.Tasks.Task.ThrowAsync.AnonymousMethod__128_0(object) in Task.cs
...
[Call Stack Truncated]

Inner Exception 1:
NotSupportedException: Specified method is not supported.

=> I show you the debug output:
Grabbing from https://content.jwplatform.com/manifests/yp34SRmf.m3u8...
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\PrivateAssemblies\Runtime\Microsoft.VisualStudio.Debugger.Runtime.NetCoreApp.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\IntelliTrace\Microsoft.IntelliTrace.TelemetryObserver.Common.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\IntelliTrace\Microsoft.IntelliTrace.TelemetryObserver.CoreClr.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Reflection.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Reflection.Extensions.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.Extensions.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Net.Sockets.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Net.NameResolution.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Threading.ThreadPool.dll'.
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.Intrinsics.dll'.

=== Streams ===

=== Downloading ===
7 segments
Duration: 00:00:25.5000000
Downloading segment #1 no desc... OK
Downloading segment #2 no desc... OK
Downloading segment #3 no desc... OK
Downloading segment #4 no desc... OK
Downloading segment #5 no desc... OK
Downloading segment #6 no desc... OK
Downloading segment #7 no desc... OK
'HLSDownloadTestForm.exe' (CoreCLR: clrhost): Loaded 'D:\RaceOCR\HLSVideo\HLSDownloadTestForm\HLSDownloadTestForm\bin\Debug\net8.0-windows\HLSDownload.Converter.dll'. Symbols loaded.
All segments were downloaded successfully.
Exception thrown: 'System.NotSupportedException' in FFmpeg.AutoGen.dll
Cleaned up temp files.
Exception thrown: 'System.NotSupportedException' in System.Private.CoreLib.dll
Exception thrown: 'System.NotSupportedException' in System.Private.CoreLib.dll
Exception thrown: 'System.NotSupportedException' in System.Private.CoreLib.dll
Exception thrown: 'System.Reflection.TargetInvocationException' in System.Private.CoreLib.dll
Exception thrown: 'System.Reflection.TargetInvocationException' in System.Private.CoreLib.dll
Exception thrown: 'System.Reflection.TargetInvocationException' in System.Windows.Forms.dll
An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in System.Windows.Forms.dll
Exception has been thrown by the target of an invocation.

Extracted embedded document "System.Reflection\MethodBaseInvoker.cs" to "C:\Users\John\AppData\Local\Temp.vsdbgsrc\106739112c966cc39d25dd039145ce9de2cff17182935e358a18dd53276b70ce\MethodBaseInvoker.cs"

I can show you some related code for this part: public void Build()
{
AVStream*[] streams = new AVStream*[2];
Dictionary<MediaStreamType, int> stream_dic = [];
ValidateArguments();
// create decoders
Dictionary<MediaStreamType, MediaDecoder> decoders = MakeDecoders();
try
{
// open output iocontext
using IOContext output = new(OutputPath, ffmpeg.AVIO_FLAG_WRITE);
// open muxer
using MediaMuxer muxer = new(output, OutputShortName, OutputMimeType);
// add streams
int index = 0;
foreach (KeyValuePair<MediaStreamType, MediaDecoder> decoderPair in decoders)
{
MediaDecoder decoder = decoderPair.Value;
AVCodecID targetCodec = decoder.CodecId;
AVCodecContext* decoderCodec = decoder.CodecContext;
switch (decoderPair.Key)
{
case MediaStreamType.Audio:
if (TargetAudioCodec != null)
targetCodec = TargetAudioCodec.Value;
break;
case MediaStreamType.Video:
if (TargetVideoCodec != null)
targetCodec = TargetVideoCodec.Value;
break;
}
AVCodec* encoder = ffmpeg.avcodec_find_encoder(targetCodec);
AVStream* outStream = muxer.AddStream(encoder);
AVCodecParameters* param = outStream->codecpar;
streams[index] = outStream;
stream_dic.Add(decoderPair.Key, index++);

  AVCodecContext* codecContext = ffmpeg.avcodec_alloc_context3(encoder);
  if (codecContext == null)
  {
    // Handle error
  }

  if (ffmpeg.avcodec_parameters_to_context(codecContext, outStream->codecpar) < 0)
  {
    // Handle error
  }
  if (decoder.CodecId == targetCodec)
  {
    // converting to the same codec
    ffmpeg.avcodec_parameters_from_context(param, codecContext).ThrowOnError();
  }
  else
  {
    // converting to another codec
    switch (decoderPair.Key)
    {
      case MediaStreamType.Audio:
        param->codec_id = targetCodec;
        param->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO;
        param->sample_rate = decoderCodec->sample_rate;
        outStream->time_base = decoderCodec->time_base;
        break;
      case MediaStreamType.Video:
        throw new NotSupportedException();
    }
  }
  outStream->codecpar->codec_tag = 0;
}
// write headers
muxer.WriteHeader();
// write packets
long audio_dts = ffmpeg.AV_NOPTS_VALUE;
long video_dts = ffmpeg.AV_NOPTS_VALUE;
long audio_pts = 0, video_pts = 0;
KeyValuePair<MediaStreamType, MediaDecoder> audio_stream = 
  decoders.Where(pair => pair.Key == MediaStreamType.Audio).First();
KeyValuePair<MediaStreamType, MediaDecoder> video_stream = 
  decoders.Where(pair => pair.Key == MediaStreamType.Video).First();
bool any_audio = true, any_video = true;

while (true)
{
  bool anyPacket = false;
  while (any_audio || any_video)
  {
    KeyValuePair<MediaStreamType, MediaDecoder> decoderPair;
    // choose a decoder pair
    if (!any_audio)
      decoderPair = video_stream;
    else if (!any_video)
      decoderPair = audio_stream;
    else
    {
      // choose between audio and video
      decoderPair = video_dts < audio_dts ? video_stream : audio_stream;
    }
    // decoder is chosen now,
    // let's read and encode
    MediaDecoder decoder = decoderPair.Value;
    int stream_index = stream_dic[decoderPair.Key];
    AVStream* outputStream = streams[stream_index];
    if (decoder.CodecId == outputStream->codecpar->codec_id)
    {
      // simply copy to target stream
      using MediaPacket inputFrame = decoder.ReadPacket();
      if (inputFrame == null)
      {
        if (decoder == audio_stream.Value)
          any_audio = false;
        else
          any_video = false;
        continue;
      }
      AVPacket* pck = inputFrame.Pointer;
      ffmpeg.av_packet_rescale_ts(pck, decoder.TimeBase, outputStream->time_base);
      pck->stream_index = stream_index;
      long* last_dts, last_pts;
      switch (decoder.CodecContext->codec_type)
      {
        case AVMediaType.AVMEDIA_TYPE_AUDIO:
          last_dts = &audio_dts;
          last_pts = &audio_pts;
          break;

        case AVMediaType.AVMEDIA_TYPE_VIDEO:
          last_dts = &video_dts;
          last_pts = &video_pts;
          break;
        default:
          throw new NotSupportedException();
      }

      if (pck->dts < (*last_dts + ((muxer.FormatContextPtr->oformat->flags & ffmpeg.AVFMT_TS_NONSTRICT) > 0 ? 0 : 1)) && pck->dts != ffmpeg.AV_NOPTS_VALUE && *last_dts != ffmpeg.AV_NOPTS_VALUE)
      {
        long next_dts = (*last_dts) + 1;
        if (pck->pts >= pck->dts && pck->pts != ffmpeg.AV_NOPTS_VALUE)
          pck->pts = Math.Max(pck->pts, next_dts);
        if (pck->pts == ffmpeg.AV_NOPTS_VALUE)
          pck->pts = next_dts;
        pck->dts = next_dts;
      }
        (*last_dts) = pck->dts;
      muxer.WritePacket(pck);
      anyPacket = true;
    }
    else
      throw new NotSupportedException("Format conversion is not supported.");
  }
  if (!anyPacket)
    break;
}

// write trailer
muxer.WriteTrailer();

}

finally
{
// dispose decoders
foreach (MediaDecoder decoder in decoders.Values)
decoder.Dispose();
}
}

Please advice if you think it is due to the new version of Ffmpeg.AutoGen, the new version is 7.0.0; but your original repo has the Ffmpeg.AutoGen version of 4.4.1.1; I think the new version should be better, since it will be more stable than old version.
But I doubt that the new version has introduced some break changes, so I can't run my test code. Please take a look at the related code to see if you can fix this new issue?
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant