Wiedergabe mehrerer versetzter Audiodateien mit Wiedergabegeschwindigkeit und Suchfunktionen
Posted: 05 Jan 2025, 17:16
Stellen Sie sich einen einfachen Audio-Editor vor, bei dem Sie mehrere Audiosegmente in mehreren Spuren haben können.
Diese Segmente können unterschiedliche Start-, Längen- und Wiedergabegeschwindigkeiten haben, so wie jeder Audio-/Video-Editor funktioniert.
Ich versuche, die Audiowiedergabe dieser Audiosegmente mit NAudio zu implementieren.
So initialisiere ich die notwendigen Helfer:
Und so mische ich die Audiospuren. Ich denke, ich soll diese Methode jedes Mal aufrufen, wenn ein Audiosegment geändert wird (wenn hinzugefügt, gelöscht, auf einen anderen Zeitstempel gezogen usw.):
BEARBEITEN:
Das ist meiner Meinung nach der einzige Weg, nach dem man suchen kann:
Aber das Problem besteht darin, dass der Mixer nach Ablauf der Gesamtspielzeit seine Eingänge entfernt. Auch wenn ich zurück zur Null suche.
Wenn ich beispielsweise bei einer 10-Sekunden-Aufnahme 5 Sekunden abspiele und dann wieder zu 00:00 gehe, wird der Anfang des Audios noch einmal abgespielt, aber nur für 5 Sekunden.< /p>
Überprüfe den _mixer und sehe, dass die Eingaben gelöscht wurden.
EDIT 2:
Okay, ich habe es zum Laufen gebracht, aber Sieht chaotisch aus.
Wird immer noch auf Fehler getestet.
Ich wünschte, es gäbe eine CurrentTime-Eigenschaft im MixingSampleProvider, das würde die Sache einfacher machen.
Die Methode zum Füllen des Puffers wird immer bei jedem Renderzyklus aufgerufen, füllt den Puffer jedoch nur dann wirklich auf, wenn es nötig ist.
Der Wert von 10_000_000 Ticks ist eine Schätzung. Der Puffer muss geladen werden, wenn er sich dem Ende nähert.
Diese Segmente können unterschiedliche Start-, Längen- und Wiedergabegeschwindigkeiten haben, so wie jeder Audio-/Video-Editor funktioniert.
Ich versuche, die Audiowiedergabe dieser Audiosegmente mit NAudio zu implementieren.
So initialisiere ich die notwendigen Helfer:
Code: Select all
public EditorViewModel()
{
_outputDevice = new WaveOutEvent();
_mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(48000, 2))
{
ReadFully = true
};
_bufferedWaveProvider = new BufferedWaveProvider(_mixer.WaveFormat);
_outputDevice.Init(_bufferedWaveProvider);
}
Code: Select all
private void MixAudioTracks()
{
_mixer.RemoveAllMixerInputs();
_audioFileReaders = [];
foreach (var track in Tracks)
{
foreach (var segment in track.Segments)
{
var inputStream = new AudioFileReader(segment.AudioPath)
{
Volume = segment.Volume
};
var offsetProvider = new OffsetSampleProvider(inputStream.ToSampleProvider())
{
DelayBy = TimeSpan.FromTicks(segment.StartTime),
SkipOver = TimeSpan.FromTicks(segment.StartTimeOffset),
Take = TimeSpan.FromTicks(segment.Length - segment.EndTimeOffset)
};
var pitchProvider = new SmbPitchShiftingSampleProvider(offsetProvider)
{
PitchFactor = segment.SpeedFactor / SpeedFactor //Segment vs previewer speed
};
_audioFileReaders.Add(inputStream);
_mixer.AddMixerInput(pitchProvider);
}
}
FillBuffer(true);
}
Das ist meiner Meinung nach der einzige Weg, nach dem man suchen kann:
Aber das Problem besteht darin, dass der Mixer nach Ablauf der Gesamtspielzeit seine Eingänge entfernt. Auch wenn ich zurück zur Null suche.
Code: Select all
private void SeekAudio()
{
foreach (var fileReader in _audioFileReaders)
{
fileReader.CurrentTime = TimeSpan.FromTicks(CurrentTime);
//TODO: Adjust with segment Start and Offsets
}
FillBuffer(true);
}
private void FillBuffer(bool force = false)
{
if (!force && _lastSampleTime >= 0 && (_lastSampleTime < CurrentTime || _lastSampleTime + _bufferedWaveProvider.BufferDuration.Ticks < CurrentTime))
return;
//if (force || CurrentTime < _lastSampleTime)
_bufferedWaveProvider.ClearBuffer();
var floatBuffer = new float[_bufferedWaveProvider.BufferLength / 4];
var bytesRead = _mixer.Read(floatBuffer, 0, floatBuffer.Length);
//Convert float array to byte array
var byteBuffer = new byte[bytesRead * 4]; //4 bytes per float
Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length);
_bufferedWaveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);
//Update the last sample timestamp.
_lastSampleTime = CurrentTime;
}
Überprüfe den _mixer und sehe, dass die Eingaben gelöscht wurden.
EDIT 2:
Okay, ich habe es zum Laufen gebracht, aber Sieht chaotisch aus.
Wird immer noch auf Fehler getestet.
Ich wünschte, es gäbe eine CurrentTime-Eigenschaft im MixingSampleProvider, das würde die Sache einfacher machen.
Code: Select all
private void SeekAudio(long seekTo)
{
CurrentTime = seekTo;
MixAudioTracks();
FillBuffer(true);
}
private void MixAudioTracks()
{
_mixer.RemoveAllMixerInputs();
foreach (var track in Tracks)
{
foreach (var segment in track.Segments)
{
//If audio segment is out of range, ignore it.
if (CurrentRenderTime > segment.StartTime + segment.Length)
continue;
var inputStream = new AudioFileReader(segment.AudioPath)
{
Volume = segment. Volume, //0f to 1f
};
var offsetProvider = new OffsetSampleProvider(inputStream.ToSampleProvider());
//Audio segment needs to be played at the exact time, based on its position, length, trims/offset
if (segment.StartTime < CurrentTime)
{
offsetProvider.SkipOver = TimeSpan.FromTicks(CurrentRenderTime - segment.StartTime + segment.StartTimeOffset);
offsetProvider.Take = TimeSpan.FromTicks(segment.Length - segment.EndTimeOffset - CurrentTime - segment.StartTime);
}
else
{
offsetProvider.DelayBy = TimeSpan.FromTicks(segment.StartTime - CurrentTime);
offsetProvider.SkipOver = TimeSpan.FromTicks(segment.StartTimeOffset);
offsetProvider.Take = TimeSpan.FromTicks(segment.Length - segment.EndTimeOffset);
}
var pitchProvider = new SmbPitchShiftingSampleProvider(offsetProvider)
{
PitchFactor = segment.SpeedFactor / (float)PreviewerSpeedFactor
};
_mixer.AddMixerInput(pitchProvider);
}
}
}
Der Wert von 10_000_000 Ticks ist eine Schätzung. Der Puffer muss geladen werden, wenn er sich dem Ende nähert.
Code: Select all
private void FillBuffer(bool force = false)
{
//Only fill buffer if forced, never filled, current time in past or current time past buffer length.
if (!force && _lastSampleTime >= 0 && (_lastSampleTime > CurrentRenderTime ||
_lastSampleTime + _bufferedWaveProvider.BufferDuration.Ticks - 10_000_000 > CurrentTime))
return;
_bufferedWaveProvider.ClearBuffer();
var floatBuffer = new float[_bufferedWaveProvider.BufferLength / 4];
var bytesRead = _mixer.Read(floatBuffer, 0, floatBuffer.Length);
//Convert float array to byte array
var byteBuffer = new byte[bytesRead * 4]; //4 bytes per float
Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length);
_bufferedWaveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);
//Update the last sample timestamp.
_lastSampleTime = CurrentTime;
}