Проблема с записью бинарных данных с помощью ofstream

Привет всем, я пишу приложение, которое записывает вход микрофона в файл WAV. Раньше я писал это для заполнения буфера заданного размера, и это работало нормально. Теперь я хотел бы иметь возможность записывать произвольную длину. Вот что я пытаюсь сделать:

  • Настройте 32 небольших аудиобуфера (круговая буферизация)
  • Запустите файл WAV с параметром ofstream -- напишите заголовок с длиной PCM, установленной на 0
  • Добавить буфер для ввода
  • Когда буфер завершается, добавьте его данные в файл WAV и обновите заголовок; переработать буфер
  • Когда пользователь нажмет «стоп», запишите оставшиеся буферы в файл и закройте

Это как бы работает, поскольку файлы имеют правильную длину (заголовок и размер файла и являются правильными). Тем не менее, данные шаткие, как черт. Я могу разобрать подобие того, что я сказал, и синхронизация правильная, но есть этот повторяющийся блок искажений. По сути, это звучит так, как будто только половина данных попадает в файл.

Вот некоторые переменные, которые использует код (в заголовке)

// File writing
ofstream mFile;
WAVFILEHEADER mFileHeader;
int16_t * mPcmBuffer;
int32_t mPcmBufferPosition;
int32_t mPcmBufferSize;
uint32_t mPcmTotalSize;
bool mRecording;

Вот код, который подготавливает файл:

// Start recording audio
void CaptureApp::startRecording()
{

    // Set flag
    mRecording = true;

    // Set size values
    mPcmBufferPosition = 0;
    mPcmTotalSize = 0;

    // Open file for streaming
    mFile.open("c:\my.wav", ios::binary|ios::trunc);

}

Вот код, который получает буфер. Это предполагает, что входящие данные верны — они должны быть такими, но я не исключаю, что это не так.

// Append file buffer to output WAV
void CaptureApp::writeData()
{

    // Update header with new PCM length
    mPcmBufferPosition *= sizeof(int16_t);
    mPcmTotalSize += mPcmBufferPosition;
    mFileHeader.bytes = mPcmTotalSize + sizeof(WAVFILEHEADER);
    mFileHeader.pcmbytes = mPcmTotalSize;
    mFile.seekp(0);
    mFile.write(reinterpret_cast<char *>(&mFileHeader), sizeof(mFileHeader));

    // Append PCM data
    if (mPcmBufferPosition > 0)
    {
        mFile.seekp(mPcmTotalSize - mPcmBufferPosition + sizeof(WAVFILEHEADER));
        mFile.write(reinterpret_cast<char *>(&mPcmBuffer), mPcmBufferPosition);
    }

    // Reset file buffer position
    mPcmBufferPosition = 0;

}

И это код, который закрывает файл:

// Stop recording
void CaptureApp::stopRecording()
{

    // Save remaining data
    if (mPcmBufferSize > 0) 
        writeData();

    // Close file
    if (mFile.is_open())
    {
        mFile.flush();
        mFile.close();
    }

    // Turn off recording flag
    mRecording = false;

}

Если здесь есть что-то, что может привести к тому, что в файл будут добавлены неверные данные, пожалуйста, дайте мне знать. Если нет, я трижды проверю входные данные (в обратном вызове). Эти данные должны быть хорошими, потому что они работают, если я скопирую их в больший буфер (например, две минуты), а затем сохраню их.


person BTR    schedule 11.02.2011    source источник
comment
Это [ mPcmBufferPosition *= sizeof(int16_t); ] похоже на опечатку. Это должно быть += ?   -  person Chris K    schedule 11.02.2011


Ответы (4)


Мне просто интересно, как

void CaptureApp::writeData()
{
    mPcmBufferPosition *= sizeof(int16_t); // mPcmBufferPosition = 0, so 0*2 = 0;

// (...)
    mPcmBufferPosition = 0;

}

работает (кстати, sizeof int16_t всегда равен 2). Вы устанавливаете mPcmBufferPosition где-то еще?

void CaptureApp::writeData()
{

    // Update header with new PCM length
    long pos = mFile.tellp();
    mPcmBufferBytesToWrite *= 2;
    mPcmTotalSize += mPcmBufferBytesToWrite;
    mFileHeader.bytes = mPcmTotalSize + sizeof(WAVFILEHEADER);
    mFileHeader.pcmbytes = mPcmTotalSize;

    mFile.seekp(0);
    mFile.write(reinterpret_cast<char *>(&mFileHeader), sizeof(mFileHeader));
    mFile.seekp(pos);

    // Append PCM data
    if (mPcmBufferBytesToWrite > 0)    
        mFile.write(reinterpret_cast<char *>(mPcmBuffer), mPcmBufferBytesToWrite);
}

Также mPcmBuffer является указателем, поэтому не знаю, почему вы используете & при записи.

person Pawel Zubrycki    schedule 11.02.2011
comment
mPcmBuffer раньше был константой int16_t (когда файловый буфер был большой фиксированной длины), а не указателем. Я, очевидно, пропустил это, когда начал стримить в файл. - person BTR; 11.02.2011

Наиболее вероятная причина в том, что вы пишете с адреса указателя на ваш буфер, а не из самого буфера. Уберите «&» в конце mFile.write. (В нем могут быть хорошие данные, если ваш буфер выделен поблизости, и вам посчастливилось захватить его кусок, но это просто удача, что ваша запись перекрывает ваш буфер)

В общем, если вы окажетесь в такой ситуации, вы можете попытаться подумать, как вы можете протестировать этот код отдельно от кода записи: настроить буфер, содержащий в нем значения 0..255, а затем установить «Размер фрагмента» до 16 и посмотреть, записывает ли он непрерывную последовательность 0..255 через 16 отдельных операций записи. Это быстро проверит, работает ли ваш код буферизации или нет.

person Jason Williams    schedule 11.02.2011
comment
Это ответ, но первый постер тоже был несколькими минутами ранее. Да, это закончилось тем, что мы слишком долго смотрели на один и тот же код и слишком мало спали. Но рад, что опубликовал его на случай, если кто-то захочет узнать, как передавать аудио в WAV. :) - person BTR; 12.02.2011
comment
Кроме того, я немного упростил код для этого. sizeof(int16_t) здесь на самом деле sizeof(T) в моем коде. - person BTR; 12.02.2011

Я не буду отлаживать ваш код, но попытаюсь дать вам контрольный список вещей, которые вы можете попробовать проверить и определить, где ошибка:

  • всегда имейте под рукой референтный рекордер или плеер. Это может быть что-то такое простое, как Windows Sound Recorder, Audacity или Adobe Audition. Имейте записывающее устройство/проигрыватель, в котором вы УВЕРЕНЫ, что он будет правильно записывать и воспроизводить файлы.
  • запишите файл с помощью своего приложения и попробуйте воспроизвести его с помощью эталонного проигрывателя. Работающий?
  • попробуйте записать файл на референсный рекордер и воспроизвести на плеере. Работающий?
  • когда вы записываете данные ЗВУКА в файл WAV на диктофоне, записывайте их в один дополнительный файл. Откройте этот файл в RAW-режиме с помощью плеера (Windows Sound Recorder здесь будет недостаточно). Играет правильно?
  • при воспроизведении файла в проигрывателе и записи на звуковую карту запишите вывод в файл RAW, чтобы увидеть, правильно ли вы вообще воспроизводите данные или у вас есть проблемы со звуковой картой. Играет правильно?

Попробуйте все это, и вы гораздо лучше поймете, где что-то пошло не так.

person Daniel Mošmondor    schedule 11.02.2011

Черт, извините, я поздно вечером работал, и сегодня я немного не в себе. Я забыл показать вам настоящий обратный вызов. Это оно:

// Called when buffer is full
void CaptureApp::onData(float * data, int32_t & size)
{

    // Check recording flag and buffer size
    if (mRecording && size <= BUFFER_LENGTH)
    {

        // Save the PCM data to file and reset the array if we 
        // don't have room for this buffer
        if (mPcmBufferPosition + size >= mPcmBufferSize) 
            writeData();

        // Copy PCM data to file buffer
        copy(mAudioInput.getData(), mAudioInput.getData() + size, mPcmBuffer + mPcmBufferPosition);

        // Update PCM position
        mPcmBufferPosition += size;

    }

}

Постараюсь дать совет и отчитаюсь.

person BTR    schedule 11.02.2011