Как получить массив сэмплов из аудио файла
Я занимаюсь разработкой приложения UWP (для Windows 10), которое работает с аудиоданными. Он получает буфер сэмплов в начале в виде плавающего массива сэмплов, элементы которого меняются с -1f на 1f. Ранее я использовал NAudio.dll 1.8.0, что дает весь необходимый функционал. Работал с классами WaveFileReader, waveBuffer.FloatBuffer, WaveFileWriter. Однако, когда я закончил это приложение и попытался собрать версию Relese, я получил эту ошибку: ILT0042: Массивы типов указателей в настоящее время не поддерживаются: 'System.Int32*[]'.
Я пытался решить это:
1) https://forums.xamarin.com/discussion/73169/uwp-10-build-fail-arrays-of-pointer-types-error
Есть совет удалить ссылку на.dll, но она мне нужна.
2) Я пытался установить NAudio той же версии, используя Manage NuGet Packages, но WaveFileReader, WaveFileWriter недоступны.
3) В ответе разработчика NAudio ( Как сохранить файл.wav в Windows 10 с помощью NAudio) я читал об использовании AudioGraph, но я могу создать массив сэмплов float только при воспроизведении в реальном времени, но мне нужно получить полный пакет сэмплов сразу после загрузки аудио файла. Пример получения сэмплов во время процесса записи или воспроизведения: https://docs.microsoft.com/ru-ru/windows/uwp/audio-video-camera/audio-graphs
Вот почему мне нужна помощь: как получить FloatBuffer для работы с сэмплами после загрузки аудио файла? Например, для построения звуковой волны или расчета для применения звуковых эффектов.
Заранее спасибо.
Я пытался использовать FileStream и BitConverter.ToSingle(), однако у меня был другой результат по сравнению с NAudio. Другими словами, я все еще ищу решение.
private float[] GetBufferArray() { string _path = ApplicationData.Current.LocalFolder.Path.ToString() + "/track_1.mp3"; FileStream _stream = new FileStream(_path, FileMode.Open); BinaryReader _binaryReader = new BinaryReader(_stream); int _dataSize = _binaryReader.ReadInt32(); byte[] _byteBuffer = _binaryReader.ReadBytes(_dataSize); int _sizeFloat = sizeof(float); float[] _floatBuffer = new float[_byteBuffer.Length / _sizeFloat]; for (int i = 0, j = 0; i < _byteBuffer.Length - _sizeFloat; i += _sizeFloat, j++) { _floatBuffer[j] = BitConverter.ToSingle(_byteBuffer, i); } return _floatBuffer; }
2 ответа
Еще один способ чтения сэмплов из аудиофайла в UWP - использование AudioGraph API. Он будет работать для всех аудиоформатов, которые поддерживает Windows10
Вот пример кода
namespace AudioGraphAPI_read_samples_from_file
{
// App opens a file using FileOpenPicker and reads samples into array of
// floats using AudioGragh API
// Declare COM interface to access AudioBuffer
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
public sealed partial class MainPage : Page
{
StorageFile mediaFile;
AudioGraph audioGraph;
AudioFileInputNode fileInputNode;
AudioFrameOutputNode frameOutputNode;
/// <summary>
/// We are going to fill this array with audio samples
/// This app loads only one channel
/// </summary>
float[] audioData;
/// <summary>
/// Current position in audioData array for loading audio samples
/// </summary>
int audioDataCurrentPosition = 0;
public MainPage()
{
this.InitializeComponent();
}
private async void Open_Button_Click(object sender, RoutedEventArgs e)
{
// We ask user to pick an audio file
FileOpenPicker filePicker = new FileOpenPicker();
filePicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
filePicker.FileTypeFilter.Add(".mp3");
filePicker.FileTypeFilter.Add(".wav");
filePicker.FileTypeFilter.Add(".wma");
filePicker.FileTypeFilter.Add(".m4a");
filePicker.ViewMode = PickerViewMode.Thumbnail;
mediaFile = await filePicker.PickSingleFileAsync();
if (mediaFile == null)
{
return;
}
// We load samples from file
await LoadAudioFromFile(mediaFile);
// We wait 5 sec
await Task.Delay(5000);
if (audioData == null)
{
ShowMessage("Error loading samples");
return;
}
// After LoadAudioFromFile method finished we can use audioData
// For example we can find max amplitude
float max = audioData[0];
for (int i = 1; i < audioData.Length; i++)
if (Math.Abs(audioData[i]) > Math.Abs(max))
max = audioData[i];
ShowMessage("Maximum is " + max.ToString());
}
private async void ShowMessage(string Message)
{
var dialog = new MessageDialog(Message);
await dialog.ShowAsync();
}
private async Task LoadAudioFromFile(StorageFile file)
{
// We initialize an instance of AudioGraph
AudioGraphSettings settings =
new AudioGraphSettings(
Windows.Media.Render.AudioRenderCategory.Media
);
CreateAudioGraphResult result1 = await AudioGraph.CreateAsync(settings);
if (result1.Status != AudioGraphCreationStatus.Success)
{
ShowMessage("AudioGraph creation error: " + result1.Status.ToString());
}
audioGraph = result1.Graph;
if (audioGraph == null)
return;
// We initialize FileInputNode
CreateAudioFileInputNodeResult result2 =
await audioGraph.CreateFileInputNodeAsync(file);
if (result2.Status != AudioFileNodeCreationStatus.Success)
{
ShowMessage("FileInputNode creation error: " + result2.Status.ToString());
}
fileInputNode = result2.FileInputNode;
if (fileInputNode == null)
return;
// We read audio file encoding properties to pass them to FrameOutputNode creator
AudioEncodingProperties audioEncodingProperties = fileInputNode.EncodingProperties;
// We initialize FrameOutputNode and connect it to fileInputNode
frameOutputNode = audioGraph.CreateFrameOutputNode(audioEncodingProperties);
fileInputNode.AddOutgoingConnection(frameOutputNode);
// We add a handler achiving the end of a file
fileInputNode.FileCompleted += FileInput_FileCompleted;
// We add a handler which will transfer every audio frame into audioData
audioGraph.QuantumStarted += AudioGraph_QuantumStarted;
// We initialize audioData
int numOfSamples = (int)Math.Ceiling(
(decimal)0.0000001
* fileInputNode.Duration.Ticks
* fileInputNode.EncodingProperties.SampleRate
);
audioData = new float[numOfSamples];
audioDataCurrentPosition = 0;
// We start process which will read audio file frame by frame
// and will generated events QuantumStarted when a frame is in memory
audioGraph.Start();
}
private void FileInput_FileCompleted(AudioFileInputNode sender, object args)
{
audioGraph.Stop();
}
private void AudioGraph_QuantumStarted(AudioGraph sender, object args)
{
AudioFrame frame = frameOutputNode.GetFrame();
ProcessInputFrame(frame);
}
unsafe private void ProcessInputFrame(AudioFrame frame)
{
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Read))
using (IMemoryBufferReference reference = buffer.CreateReference())
{
// We get data from current buffer
((IMemoryBufferByteAccess)reference).GetBuffer(
out byte* dataInBytes,
out uint capacityInBytes
);
// We discard first frame; it's full of zeros because of latency
if (audioGraph.CompletedQuantumCount == 1) return;
float* dataInFloat = (float*)dataInBytes;
uint capacityInFloat = capacityInBytes / sizeof(float);
// Number of channels defines step between samples in buffer
uint step = fileInputNode.EncodingProperties.ChannelCount;
// We transfer audio samples from buffer into audioData
for (uint i = 0; i < capacityInFloat; i += step)
{
if (audioDataCurrentPosition < audioData.Length)
{
audioData[audioDataCurrentPosition] = dataInFloat[i];
audioDataCurrentPosition++;
}
}
}
}
}
}
Отредактировано: это решает проблему, потому что это читает образцы из файла в массив с плавающей точкой
Первый популярный способ получения AudioData из файла Wav.
Благодаря ответу пользователя PI Как прочитать данные из wav-файла в массив, я решил проблему с чтением wav-файла в массиве с плавающей точкой в проекте UWP. Но структура файла отличается от стандартной (может быть, только в моем проекте такая проблема), когда он записывает в файл wav с помощью AudioGraph. Это приводит к непредсказуемому результату. Мы получаем значение1263424842 вместо предсказуемого 544501094, получающего идентификатор формата. После этого все последующие значения отображаются некорректно. Я нашел правильный идентификатор, последовательно ищущий в байтах. Я понял, что AudioGraph добавляет дополнительный кусок данных в записанный файл WAV, но формат записи по-прежнему PCM. Этот дополнительный фрагмент данных выглядит как данные о формате файла, но он также содержит пустые значения, пустые байты. Я не могу найти информацию об этом, может кто-то здесь знает? Решение от PI я изменил для своих нужд. Вот что у меня есть:
using (FileStream fs = File.Open(filename, FileMode.Open))
{
BinaryReader reader = new BinaryReader(fs);
int chunkID = reader.ReadInt32();
int fileSize = reader.ReadInt32();
int riffType = reader.ReadInt32();
int fmtID;
long _position = reader.BaseStream.Position;
while (_position != reader.BaseStream.Length-1)
{
reader.BaseStream.Position = _position;
int _fmtId = reader.ReadInt32();
if (_fmtId == 544501094) {
fmtID = _fmtId;
break;
}
_position++;
}
int fmtSize = reader.ReadInt32();
int fmtCode = reader.ReadInt16();
int channels = reader.ReadInt16();
int sampleRate = reader.ReadInt32();
int byteRate = reader.ReadInt32();
int fmtBlockAlign = reader.ReadInt16();
int bitDepth = reader.ReadInt16();
int fmtExtraSize;
if (fmtSize == 18)
{
fmtExtraSize = reader.ReadInt16();
reader.ReadBytes(fmtExtraSize);
}
int dataID = reader.ReadInt32();
int dataSize = reader.ReadInt32();
byte[] byteArray = reader.ReadBytes(dataSize);
int bytesForSamp = bitDepth / 8;
int samps = dataSize / bytesForSamp;
float[] asFloat = null;
switch (bitDepth)
{
case 16:
Int16[] asInt16 = new Int16[samps];
Buffer.BlockCopy(byteArray, 0, asInt16, 0, dataSize);
IEnumerable<float> tempInt16 =
from i in asInt16
select i / (float)Int16.MaxValue;
asFloat = tempInt16.ToArray();
break;
default:
return false;
}
//For one channel wav audio
floatLeftBuffer.AddRange(asFloat);
От буфера к записи файла имеет обратный алгоритм. На данный момент это единственный правильный алгоритм работы с wav-файлами, позволяющий получать аудиоданные. Использовал эту статью, работая с AudioGraph - https://docs.microsoft.com/ru-ru/windows/uwp/audio-video-camera/audio-graphs. Обратите внимание, что вы можете установить необходимые данные в формате записи с помощью AudioEncodingQuality, переназначенного из MIC в файл.
Второй способ получения AudioData с помощью NAudio из Nugget Packages.
Я использовал класс MediaFoundationReader.
float[] floatBuffer;
using (MediaFoundationReader media = new MediaFoundationReader(path))
{
int _byteBuffer32_length = (int)media.Length * 2;
int _floatBuffer_length = _byteBuffer32_length / sizeof(float);
IWaveProvider stream32 = new Wave16ToFloatProvider(media);
WaveBuffer _waveBuffer = new WaveBuffer(_byteBuffer32_length);
stream32.Read(_waveBuffer, 0, (int)_byteBuffer32_length);
floatBuffer = new float[_floatBuffer_length];
for (int i = 0; i < _floatBuffer_length; i++) {
floatBuffer[i] = _waveBuffer.FloatBuffer[i];
}
}
Сравнивая два способа я заметил:
- Полученные значения образцов различаются на 1/1 000 000. Не могу сказать, какой путь более точный (если вы знаете, будем рады услышать);
- Второй способ получения AudioData работает и для файлов MP3.
Если вы нашли какие-либо ошибки или у вас есть комментарии по этому поводу, добро пожаловать.
Заявление об импорте
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
Внутренняя функция
AudioFileReader reader = new AudioFileReader(filename);
ISampleProvider isp = reader.ToSampleProvider();
float[] buffer = new float[reader.Length / 2];
isp.Read(buffer, 0, buffer.Length);
буферный массив будет иметь 32-битные образцы с плавающей запятой IEEE. Это использует пакет NAudio Nuget Visual Studio.