Чтобы обеспечить немного контекста. Я пытаюсь вывести живой звук с камеры в моем приложении C #. После некоторого исследования кажется вполне очевидным сделать это в dll, управляемом c ++. Я выбрал XAudio2 API, потому что он должен быть довольно простым для реализации и использования с динамическим аудиоконтентом.
Таким образом, идея состоит в том, чтобы создать устройство XAudio в c ++ с пустым буфером и вставить аудио со стороны кода c #. Звуковые блоки вставляются каждые 50 мс, потому что я хочу, чтобы задержка была как можно меньше.
// SampleRate = 44100; Channels = 2; BitPerSample = 16;
var blockAlign = (Channels * BitsPerSample) / 8;
var avgBytesPerSecond = SampleRate * blockAlign;
var avgBytesPerMillisecond = avgBytesPerSecond / 1000;
var bufferSize = avgBytesPerMillisecond * Time;
_sampleBuffer = new byte[bufferSize];
Каждый раз, когда таймер запускается, он получает указатель аудио-буфера, считывает данные из аудио, копирует данные в указатель и вызывает метод PushAudio.
Я также использую секундомер, чтобы проверить, сколько времени заняла обработка, и снова рассчитать интервал для таймера, чтобы включить время обработки.
private void PushAudioChunk(object sender, ElapsedEventArgs e)
{
unsafe
{
_pushAudioStopWatch.Reset();
_pushAudioStopWatch.Start();
var audioBufferPtr = Output.AudioCapturerBuffer();
FillBuffer(_sampleBuffer);
Marshal.Copy(_sampleBuffer, 0, (IntPtr)audioBufferPtr, _sampleBuffer.Length);
Output.PushAudio();
_pushTimer.Interval = Time - _pushAudioStopWatch.ElapsedMilliseconds;
_pushAudioStopWatch.Stop();
DIX.Log.WriteLine("Push audio took: {0}ms", _pushAudioStopWatch.ElapsedMilliseconds);
}
}
Это реализация части c ++.
Что касается документации по msdn, я создал устройство XAudio2 и добавил MasterVoice и SourceVoice. Сначала буфер пуст, потому что часть c # отвечает за передачу аудиоданных.
namespace Audio
{
using namespace System;
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
WAVEFORMATEXTENSIBLE wFormat;
XAUDIO2_BUFFER buffer = { 0 };
IXAudio2* pXAudio2 = NULL;
IXAudio2MasteringVoice* pMasterVoice = NULL;
IXAudio2SourceVoice* pSourceVoice = NULL;
WaveOut::WaveOut(int bufferSize)
{
audioBuffer = new Byte[bufferSize];
wFormat.Format.wFormatTag = WAVE_FORMAT_PCM;
wFormat.Format.nChannels = 2;
wFormat.Format.nSamplesPerSec = 44100;
wFormat.Format.wBitsPerSample = 16;
wFormat.Format.nBlockAlign = (wFormat.Format.nChannels * wFormat.Format.wBitsPerSample) / 8;
wFormat.Format.nAvgBytesPerSec = wFormat.Format.nSamplesPerSec * wFormat.Format.nBlockAlign;
wFormat.Format.cbSize = 0;
wFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
HRESULT hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (SUCCEEDED(hr))
{
hr = pXAudio2->CreateMasteringVoice(&pMasterVoice);
}
if (SUCCEEDED(hr))
{
hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wFormat,
0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL);
}
buffer.pAudioData = (BYTE*)audioBuffer;
buffer.AudioBytes = bufferSize;
buffer.Flags = 0;
if (SUCCEEDED(hr))
{
hr = pSourceVoice->Start(0);
}
}
WaveOut::~WaveOut()
{
}
WaveOut^ WaveOut::CreateWaveOut(int bufferSize)
{
return gcnew WaveOut(bufferSize);
}
uint8_t* WaveOut::AudioCapturerBuffer()
{
if (!audioBuffer)
{
throw gcnew Exception("Audio buffer is not initialized. Did you forget to set up the audio container?");
}
return (BYTE*)audioBuffer;
}
int WaveOut::PushAudio()
{
HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
if (FAILED(hr))
{
return -1;
}
return 0;
}
}
Проблема, с которой я сталкиваюсь, заключается в том, что у меня всегда есть некоторые трещины на выходе. Я попытался увеличить интервал таймера или немного увеличить размер буфера. Каждый раз один и тот же результат.
Что я делаю неправильно?
Обновить:
Я создал 3 буфера, через которые может пройти движок XAudio. Трещина ушла. Недостающая часть теперь состоит в том, чтобы заполнить буферы в нужное время из части c #, чтобы избежать буферов с теми же данными.
void Render(void* param)
{
std::vector<byte> audioBuffers[BUFFER_COUNT];
size_t currentBuffer = 0;
// Get the current state of the source voice
while (BackgroundThreadRunning && pSourceVoice)
{
if (pSourceVoice)
{
pSourceVoice->GetState(&state);
}
while (state.BuffersQueued < BUFFER_COUNT)
{
std::vector<byte> resultData;
resultData.resize(DATA_SIZE);
CopyMemory(&resultData[0], pAudioBuffer, DATA_SIZE);
// Retreive the next buffer to stream from MF Music Streamer
audioBuffers[currentBuffer] = resultData;
// Submit the new buffer
XAUDIO2_BUFFER buf = { 0 };
buf.AudioBytes = static_cast<UINT32>(audioBuffers[currentBuffer].size());
buf.pAudioData = &audioBuffers[currentBuffer][0];
pSourceVoice->SubmitSourceBuffer(&buf);
// Advance the buffer index
currentBuffer = ++currentBuffer % BUFFER_COUNT;
// Get the updated state
pSourceVoice->GetState(&state);
}
Sleep(30);
}
}
XAudio2 делает не скопировать буфер исходных данных во время отправки через SubmitSourceBuffer
, Вы должны сохранить эти данные (которые находятся в памяти вашего приложения) действительными, а буфер выделен на все время, которое XAudio2 будет необходимо считывать из него для обработки данных. Это сделано для эффективности, чтобы избежать необходимости в дополнительной копии, но возлагает многопоточное бремя на то, чтобы память оставалась доступной до тех пор, пока она не закончит играть на вас. Это также означает, что вы не можете изменить буфер воспроизведения.
Ваш текущий код просто повторно использует тот же буфер, который вызывает всплывающее окно при изменении данных во время воспроизведения. Вы можете решить эту проблему с помощью двух или трех буферов, между которыми вы вращаетесь. Исходный голос XAudio2 имеет информацию о состоянии, которую вы можете использовать, чтобы определить, когда закончится воспроизведение буфера, или вы можете зарегистрироваться для явных обратных вызовов, которые сообщают вам, когда буфер больше не используется.
Увидеть Набор инструментов DirectX для аудио а также классические образцы XAudio2 для примеров использования XAudio2.
Других решений пока нет …