Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Whisper.net/WhisperProcessingException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

namespace Whisper.net;

/// <summary>
/// Exception thrown when the underlying native whisper library fails during processing.
/// </summary>
public class WhisperProcessingException : Exception
{
/// <summary>
/// Gets the native error code returned by whisper.
/// </summary>
public int ErrorCode { get; }

/// <summary>
/// Creates a new instance of <see cref="WhisperProcessingException"/>, using a descriptive message based on the error code.
/// </summary>
/// <param name="errorCode">Native error code returned by whisper.</param>
public WhisperProcessingException(int errorCode)
: base(GetErrorMessage(errorCode))
{
ErrorCode = errorCode;
}

private static string GetErrorMessage(int errorCode)
{
return errorCode switch
{
-1 => "Failed to compute voice activity detection.",
-2 => "Failed to compute log mel spectrogram.",
-3 => "Failed to auto-detect language.",
-4 => "Too many decoders requested.",
-5 => "Audio context is larger than the maximum allowed.",
-6 => "Failed to encode audio features.",
-7 => "Failed to initialize key-value cache for self-attention.",
-8 => "Failed to decode audio features.",
-9 => "Failed to decode during token processing.",
_ => $"Native whisper stopped processing with error code {errorCode}."
};
}
}
28 changes: 20 additions & 8 deletions Whisper.net/WhisperProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ public unsafe void Process(ReadOnlySpan<float> samples)
processingSemaphore.Wait();
segmentIndex = 0;

nativeWhisper.Whisper_Full_With_State(currentWhisperContext, state, whisperParams, (IntPtr)pData, samples.Length);
var result = nativeWhisper.Whisper_Full_With_State(currentWhisperContext, state, whisperParams, (IntPtr)pData, samples.Length);
if (result != 0)
{
throw new WhisperProcessingException(result);
}
}
finally
{
Expand Down Expand Up @@ -232,17 +236,17 @@ bool OnWhisperAbortHandler()
options.OnSegmentEventHandlers.Add(OnSegmentHandler);
options.WhisperAbortEventHandler = OnWhisperAbortHandler;

currentCancellationToken = cancellationToken;
var whisperTask = ProcessInternalAsync(samples, cancellationToken)
.ContinueWith(_ => resetEvent.Set(), cancellationToken, TaskContinuationOptions.None, TaskScheduler.Default);
currentCancellationToken = cancellationToken;
var processingTask = ProcessInternalAsync(samples, cancellationToken);
var whisperTask = processingTask.ContinueWith(_ => resetEvent.Set(), cancellationToken, TaskContinuationOptions.None, TaskScheduler.Default);

while (!whisperTask.IsCompleted || !buffer.IsEmpty)
while (!processingTask.IsCompleted || !buffer.IsEmpty)
{
cancellationToken.ThrowIfCancellationRequested();

if (buffer.IsEmpty)
{
await Task.WhenAny(whisperTask, resetEvent.WaitAsync())
await Task.WhenAny(processingTask, resetEvent.WaitAsync())
.ConfigureAwait(false);
}

Expand All @@ -252,7 +256,11 @@ await Task.WhenAny(whisperTask, resetEvent.WaitAsync())
}
}

await whisperTask.ConfigureAwait(false);
await processingTask.ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}

while (buffer.TryDequeue(out var segmentData))
{
Expand Down Expand Up @@ -343,7 +351,11 @@ private unsafe Task ProcessInternalAsync(ReadOnlyMemory<float> samples, Cancella

try
{
nativeWhisper.Whisper_Full_With_State(currentWhisperContext, state, whisperParams, (IntPtr)pData, samples.Length);
var result = nativeWhisper.Whisper_Full_With_State(currentWhisperContext, state, whisperParams, (IntPtr)pData, samples.Length);
if (result != 0)
{
throw new WhisperProcessingException(result);
}
}
finally
{
Expand Down
101 changes: 101 additions & 0 deletions tests/Whisper.net.Tests/ProcessingFailureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

using System.Runtime.InteropServices;
using Whisper.net.Internals.Native;
using Whisper.net.Native;
using Xunit;

namespace Whisper.net.Tests;

public class ProcessingFailureTests
{
private sealed class FakeNativeWhisper : INativeWhisper
{
private readonly int _errorCode;

public FakeNativeWhisper(int errorCode)
{
_errorCode = errorCode;
Whisper_Full_With_State = (context, state, p, samples, n) => _errorCode;
Whisper_Init_State = _ => new IntPtr(1);
Whisper_Free_State = _ => { };
Whisper_Full_Default_Params_By_Ref = strategy =>
{
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<WhisperFullParams>());
var param = new WhisperFullParams { Strategy = strategy };
Marshal.StructureToPtr(param, ptr, false);
return ptr;
};
Whisper_Free_Params = ptr => Marshal.FreeHGlobal(ptr);
Whisper_Init_From_File_With_Params_No_State = (_, _) => IntPtr.Zero;
Whisper_Init_From_Buffer_With_Params_No_State = (_, _, _) => IntPtr.Zero;
Whisper_Free = _ => { };
Whisper_Full_N_Segments_From_State = _ => 0;
Whisper_Full_Get_Segment_T0_From_State = (_, _) => 0;
Whisper_Full_Get_Segment_T1_From_State = (_, _) => 0;
Whisper_Full_Get_Segment_Text_From_State = (_, _) => IntPtr.Zero;
Whisper_Full_N_Tokens_From_State = (_, _) => 0;
Whisper_Full_Get_Token_P_From_State = (_, _, _) => 0;
Whisper_Lang_Max_Id = () => 0;
Whisper_Lang_Auto_Detect_With_State = (_, _, _, _, _) => 0;
Whisper_PCM_To_Mel_With_State = (_, _, _, _, _) => 0;
Whisper_Lang_Str = _ => IntPtr.Zero;
Whisper_Full_Lang_Id_From_State = _ => 0;
Whisper_Log_Set = (_, _) => { };
Whisper_Ctx_Init_Openvino_Encoder_With_State = (_, _, _, _, _) => { };
Whisper_Full_Get_Token_Data_From_State = (_, _, _) => default;
Whisper_Full_Get_Token_Text_From_State = (_, _, _, _) => IntPtr.Zero;
WhisperPrintSystemInfo = () => IntPtr.Zero;
Whisper_Full_Get_Segment_No_Speech_Prob_From_State = (_, _) => 0;
}

public INativeWhisper.whisper_init_from_file_with_params_no_state Whisper_Init_From_File_With_Params_No_State { get; }
public INativeWhisper.whisper_init_from_buffer_with_params_no_state Whisper_Init_From_Buffer_With_Params_No_State { get; }
public INativeWhisper.whisper_free Whisper_Free { get; }
public INativeWhisper.whisper_free_params Whisper_Free_Params { get; }
public INativeWhisper.whisper_full_default_params_by_ref Whisper_Full_Default_Params_By_Ref { get; }
public INativeWhisper.whisper_full_with_state Whisper_Full_With_State { get; }
public INativeWhisper.whisper_full_n_segments_from_state Whisper_Full_N_Segments_From_State { get; }
public INativeWhisper.whisper_full_get_segment_t0_from_state Whisper_Full_Get_Segment_T0_From_State { get; }
public INativeWhisper.whisper_full_get_segment_t1_from_state Whisper_Full_Get_Segment_T1_From_State { get; }
public INativeWhisper.whisper_full_get_segment_text_from_state Whisper_Full_Get_Segment_Text_From_State { get; }
public INativeWhisper.whisper_full_n_tokens_from_state Whisper_Full_N_Tokens_From_State { get; }
public INativeWhisper.whisper_full_get_token_p_from_state Whisper_Full_Get_Token_P_From_State { get; }
public INativeWhisper.whisper_lang_max_id Whisper_Lang_Max_Id { get; }
public INativeWhisper.whisper_lang_auto_detect_with_state Whisper_Lang_Auto_Detect_With_State { get; }
public INativeWhisper.whisper_pcm_to_mel_with_state Whisper_PCM_To_Mel_With_State { get; }
public INativeWhisper.whisper_lang_str Whisper_Lang_Str { get; }
public INativeWhisper.whisper_init_state Whisper_Init_State { get; }
public INativeWhisper.whisper_free_state Whisper_Free_State { get; }
public INativeWhisper.whisper_full_lang_id_from_state Whisper_Full_Lang_Id_From_State { get; }
public INativeWhisper.whisper_log_set Whisper_Log_Set { get; }
public INativeWhisper.whisper_ctx_init_openvino_encoder_with_state Whisper_Ctx_Init_Openvino_Encoder_With_State { get; }
public INativeWhisper.whisper_full_get_token_data_from_state Whisper_Full_Get_Token_Data_From_State { get; }
public INativeWhisper.whisper_full_get_token_text_from_state Whisper_Full_Get_Token_Text_From_State { get; }
public INativeWhisper.whisper_print_system_info WhisperPrintSystemInfo { get; }
public INativeWhisper.whisper_full_get_segment_no_speech_prob_from_state Whisper_Full_Get_Segment_No_Speech_Prob_From_State { get; }

public void Dispose() { }
}

[Fact]
public void Process_WhenNativeFails_ShouldThrow()
{
var options = new WhisperProcessorOptions { ContextHandle = IntPtr.Zero };
using var processor = new WhisperProcessor(options, new FakeNativeWhisper(5));
Assert.Throws<WhisperProcessingException>(() => processor.Process(new float[1]));
}

[Fact]
public async Task ProcessAsync_WhenNativeFails_ShouldThrow()
{
var options = new WhisperProcessorOptions { ContextHandle = IntPtr.Zero };
await using var processor = new WhisperProcessor(options, new FakeNativeWhisper(7));
await Assert.ThrowsAsync<WhisperProcessingException>(async () =>
{
await foreach (var _ in processor.ProcessAsync(new float[1]))
{
}
});
}
}
Loading