Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Программирование звука > Работа со звуком с помощью MMSystem


Автор: tumanovalex 10.03.11, 09:52
Начал с самого начала, попробовал вывести параметры устройств ввода своей звуковой карты. Код:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void getnumdevaudio(UINT &nin, UINT &nout)
    {
      nin  = waveInGetNumDevs();
      nout = waveOutGetNumDevs();
    }  
     
    void getinaudioinfo(UINT nin)
    {
      WAVEINCAPS wvinfo;
      for(UINT i = 0; i < nin; i++)
      {
        MMRESULT wvres = waveInGetDevCaps(i, &wvinfo, sizeof(wvinfo));
        if(wvres != MMSYSERR_NOERROR)
        {
          std::cout << "Ошибка определения параметров устройства № "  << i << std::endl;
        } // if(wvres
          _tprintf(TEXT("%s\t%i\t%i\n"), wvinfo.szPname, wvinfo.dwFormats, wvinfo.wChannels);
      } // for(UINT
      std::cin.get();
    } // fun
     
    #include <windows.h>
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <tchar.h>
    #include "mysound.h"
     
    void main(int argc, char* argv[])
    {
      setlocale(LC_ALL, "");
      UINT nin, nout;
      getnumdevaudio(nin, nout);
      getinaudioinfo(nin);
    }

В результате получил:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    IA HD Audio CD In   786431  65535
    VIA HD Audio Front Microphone   786431  65535
    VIA HD Audio Line In    786431  65535
    VIA HD Audio Microphone 786431  65535
    VIA HD Audio Stereo Mixer   786431  65535
Возникли следующие вопросы:
1. Почему wvinfo.wChannels дает большое целое значение, а не значение 1 или 2, как указано в MSDN?
2. Как из цифры, которые дает wvinfo.dwFormats, получить все возможные параметры устрройства ввода, например WAVE_FORMAT_1M08 и др., которые устройство ввода поддерживает?

Автор: Prince 10.03.11, 16:23
1. Потому что такое значение возвращает драйвер устройства. Почему производители посчитали, что он должен возвращать именно такое значение...да кто бы знал бы.
2. Проверять, установлен ли соотвествующий флаг в dwFormats.
Цитата
#DEFINE WAVE_FORMAT_1M08 0x00000001
#DEFINE WAVE_FORMAT_1S08 0x00000002
#DEFINE WAVE_FORMAT_1M16 0x00000004
#DEFINE WAVE_FORMAT_1S16 0x00000008
...


Имхо, не стоит dwFormats воспринимать слишком серьезно. "Стандартные комбинации" параметров давно стали общепринятыми, и современными картами(драйверами) поддерживаются. Кроме того, константы для форматов 96/192 кГц 24/32 бита и многоканальных, вряд ли существуют, судя по "конструкции" 32-х битного слова dwFormats. На мой взгляд, надежней проверять поддержку формата, открывая устройство с параметрами, валидность которых под вопросом.

Цитата
IA HD Audio CD In 786431 65535
VIA HD Audio Front Microphone 786431 65535
VIA HD Audio Line In 786431 65535
VIA HD Audio Microphone 786431 65535
VIA HD Audio Stereo Mixer 786431 65535

:wacko: семерка?

Автор: tumanovalex 12.03.11, 13:56
Нет, у меня Windows XP. Компьютер с какой-то навороченной мультимедийной звуковой картой. У меня возникли следующие вопросы:
1. Начинаю пробовать записать звук:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    int tryrec(UINT nin, DWORD nbuf) // nin = 0 (домашний компьтер с одним устройством ввода - микрофоном), nbuf = 1024 - размер буфера для записи звука
    {
      TCHAR serror[128];
      serror[0] = 0;
      MMRESULT wvres;
      HWAVEIN hwvindev;
      WAVEFORMATEX wvformat;
      wvformat.wFormatTag = WAVE_FORMAT_PCM;
      wvformat.nChannels = 1;
      wvformat.nSamplesPerSec = 44100;
      wvformat.wBitsPerSample = 16;
      wvformat.nBlockAlign = wvformat.nChannels*wvformat.wBitsPerSample/8;
      wvformat.nAvgBytesPerSec = wvformat.nSamplesPerSec*wvformat.nBlockAlign;
      wvformat.cbSize = sizeof(wvformat);
      wvres = waveInOpen( &hwvindev, nin, &wvformat, 0, 0, WAVE_FORMAT_DIRECT);
      if(wvres != MMSYSERR_NOERROR)
      {
        printf("Устройство %i открыть не удалось!\n", nin);
        return 1;
      } // if(wvres
      char *swvbuf  = (char*)malloc((size_t) nbuf);
      swvbuf[0] = 0;
      WAVEHDR wvbuf;
      wvbuf.lpData          = swvbuf;
      wvbuf.dwBufferLength  = nbuf;
      wvres =  waveInPrepareHeader(hwvindev, &wvbuf, sizeof(wvbuf));
      if(wvres != MMSYSERR_NOERROR)
      {
        printf("Не удалось подготовить буфер для записи!\n");
        wvres = waveInGetErrorText(wvres, serror, 128);
        _tprintf(TEXT("Ошибка: %s\n"), serror);
        return 2;
      } // if(wvres
      return 0;  
    }
Получаю ошибку в waveInPrepareHeader "Системной функции передан неправильный параметр". Подскажите, пожалуйста, что я делаю неправильно.
2. Не очень понимаю механизм записи звука. Подготавливаю буфер, делаю waveInAddBuffer, тем самым уведомляю драйвер, что туда можно записывать данные. После заполнения буфера как мне в программе узнать, что он заполнился и надо его очистить и снова подготовить для новой записи?
3. Сколько может быть буферов и можно ли их записывать последовательно, а очищать и подготавливать за один прием после заполнения последнего? Или так не делается и нужно в процессе записи в следующий буфер готовить предыдущий?
4. Есть ли функция API для записи данных из буфера в файл? Или для записи нужно использовать обычные функции записи буфера swvbuf в файл?
5. Исходя из каких соображений нужно выбирать размер буфера?

Автор: Prince 12.03.11, 17:05
1. Скорее всего, ошибка в sizeof(wvbuf). Нужен размер заголовка sizeof(WAVEHDR), а не размер переменной.
2. Вы не указали механизм уведомления в waveinopen, что равносильно callback_null. В таком варианте вы никак не узнаете. Читайте про механизмы уведомлений звуковой подсистемы.
3. Сколько может быть буферов, я не знаю, много может быть. Когда буфер заполнен, вы получаете уведомление(в это время записывается следующий в очереди буфер). В зависимости от механизма уведомления, в оконной процедуре или в специальной функции, или в потоке, вы обрабатываете буфер, вернувшийся из очереди, и снова ставите в очередь при помощи waveinaddbuffer( или уничтожаете, если он вам уже не нужен).
4. Буфер - кусок памяти, начинающийся с адреса wvbuf.lpData. Для записи в файл можно использовать все доступные функции для работы с файлами. Для записи RIFF wave файла существуют специализированные функции mmio...
5. Исходя из потребностей. Маленький буфер обеспечит меньшую задержку и более быструю реакцию вашей программы, если ваша программа должна каким-то образом реагировать на параметры звука. Но, ваша программа не сможет реагировать на события происходящие в системе чаще, чем получает управление ваш процесс. Поэтому размер буфера снизу ограничен как минимум квантом времени виндовс. Если буфер оооочень большой, а ваш компьютер ооочень медленный, в этом случае комп может не успеть сделать с буфером все что необходимо до того, как из очереди вернется следующий буфер. Хоть и гипотетически, но такая ситуация возможна, в принципе. Сверху размер буфера ограничен объемом оперативной памяти и быстродействием компа.
Если без "воды", размер 0.1 с - 1.0 с вполне нормально, имхо.

Автор: tumanovalex 12.03.11, 18:11
Добавил в код
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      wvbuf.dwFlags         = WHDR_PREPARED;
сообщение об ошибке исчезло, видимо из-за неопределения флага оно появлялось. Однако остальные вопросы остаются актуальными.

Автор: Prince 12.03.11, 18:13
Цитата
wvbuf.dwFlags = WHDR_PREPARED;

Неверно. Это флаг пользователь не устанавливает. Флаги можно просто обнулять.
Сравните значения sizeof(wvbuf) и sizeof(WAVEHDR).

Автор: tumanovalex 13.03.11, 18:24
Проверил размеры:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    printf("%i\t%i\n", sizeof(WAVEHDR), sizeof(wvbuf));
Получилось, что они одинаковые (32). Попробовал убрать
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    wvbuf.dwFlags = WHDR_PREPARED;
опять появилась ошибка. В MSDN
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    The lpData, dwBufferLength, and dwFlags members must be set before calling the waveInPrepareHeader or waveOutPrepareHeader function.
Вроде бы получается, что в программе нужно устанавливать этот флаг.

Добавлено
Мне не очень понятно по поводу буфера. Я задаю один буфер, его размеры и другие параметры. Достаточно указать только один буфер, а драйвер сам создаст нужное количество буферов? Или мне нужно завать количество буферов самому? Например, так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    WAVEHDR wvbuf1, wvbuf2, wvbuf3

Автор: Prince 13.03.11, 20:14
Цитата
The lpData, dwBufferLength, and dwFlags members must be set before calling the waveInPrepareHeader or waveOutPrepareHeader function.

The lpData, dwBufferLength, and dwFlags members of the WAVEHDR structure must be set before calling this function (dwFlags must be zero).

Цитата
Получилось, что они одинаковые (32)

Значит, мое предположение было ошибочным. А если обнулить флаги?

Цитата
Мне не очень понятно по поводу буфера. Я задаю один буфер, его размеры и другие параметры. Достаточно указать только один буфер, а драйвер сам создаст нужное количество буферов? Или мне нужно завать количество буферов самому?

Вы описываете все буферы. Не количество, а ВСЕ буферы. Как минимум необходимы два буфера.

Автор: tumanovalex 14.03.11, 12:25
Спасибо за помощь. С обнуленным флагом все работает. Опять возникли вопросы (вызваны они наверное тем, что раньше с CALLBACK функциями в явном виде не приходилось работать):
1. У меня консольное приложение. В каком месте функции tryrec нужно описывать функцию уведомления? Непосредственно вместо нуля в определении функции waveInOpen или в виде отдельной фунции в заголовочном файле? В последнем случае в waveInOpen нужно указывать указатель на функцию уведомления?
2. Как организовать в консольном приложении оповещение о заполненности буферов?

Автор: medved_68 14.03.11, 13:12
Цитата tumanovalex @
Как организовать в консольном приложении оповещение о заполненности буферов?

Добавить в приложение функцию:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    //Процедура CallBack
    procedure CallBackZvyk(HWAVEx:THandle;UINT:Cardinal;Instance:DWORD;
                  Param1:DWORD;Param2:DWORD);stdcall;
    var
      Hdr:PWaveHdr;I: Integer;
    begin
      if UINT = WOM_DONE then
При открытии устройства:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      Err := WaveOutOpen(WaveOut, 0, @WaveFormat,
        Cardinal(@CallBackZvyk), 0, CALLBACK_FUNCTION);
Система сама вызовет твою функцию и ты сможешь проверить, с каким флагом вызвана данная функция и предпринять необходимые действия по изъятию данных из буфера (при записи) или заливке данных в буфер (при воспроизведении). :D

Автор: tumanovalex 15.03.11, 08:10
Видимо, я опять что-то не понял. Попытка использования функции окончилась неудачей. Сделал пустую функцию:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
    {
      switch(uMsg)
      {
        case WIM_OPEN:
          break;
        case WIM_DATA:
          break;
        case WIM_CLOSE:
          break;
      }
    }
Вызов функции
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    wvres = waveInOpen( &hwvindev, nin, &wvformat, (DWORD_PTR) (*waveInProc), 0, WAVE_FORMAT_DIRECT | СALLBACK_FUNCTION);
окончился необработанным исключением. Подскажите, пожалуйста, как правильно вызывать CALLBACK функцию в моем случае.

Автор: Prince 15.03.11, 18:06
Я не сишник. Возможно, проблема в dword_ptr вместо dword. Или в (DWORD_PTR) (*waveInProc). Без понятия.
Кстати, флаг WAVE_FORMAT_DIRECT не нужен, поскольку вы запрашиваете данные непосредственно от устройства (nin=0) и в формате WAVE_FORMAT_PCM. АСМ при любом раскладе задействован не будет. Если запрошенный формат не подерживается, устройство просто не удастся открыть. WaveInOpen завершится с ошибкой WAVERR_BADFORMAT.

Автор: medved_68 16.03.11, 10:35
Цитата tumanovalex @
Подскажите, пожалуйста, как правильно вызывать CALLBACK функцию в моем случае.

Цитата tumanovalex @
Видимо, я опять что-то не понял.

Видимо.... Ты не будешь вызывать эту функцию. Ее будет вызывать СИСТЕМА. А для этого необходимо (в первую очередь) соблюсти СОГЛАШЕНИЕ О ВЫЗОВАХ данной функции. Это выполнено? :)

Добавлено
Цитата tumanovalex @
Вызов функции

Это не ВЫЗОВ функции. Это всего лишь попытка ОТКРЫТЬ устройство с определенными параметрами. И судя по:
Цитата tumanovalex @
окончился необработанным исключением

параметры не соответствуют заявленным системой. :D

Автор: tumanovalex 16.03.11, 18:33
Я неправильно сформулировал, имел в виду, как правильно записать свою функцию и сделать на нее ссылку в waveInOpen.

Автор: tumanovalex 18.03.11, 07:41
Как оказалось, ошибка возникает в строке
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      wvres = waveInStart(hwvindev);

До начала записи я делаю следующее:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      char *swvbuf[16];
      for(UINT i = 0; i < nbuf; i++)
      {
        swvbuf[i]    = (char*)malloc((size_t) nbufsize);
        swvbuf[i][0] = 0;
        wvbuf.lpData          = swvbuf[i];
        wvbuf.dwFlags         = 0;
        wvbuf.dwLoops         = 0;
        wvbuf.dwUser          = 0;
        wvbuf.dwBufferLength  = nbufsize;
        wvres =  waveInPrepareHeader(hwvindev, &wvbuf, sizeof(wvbuf)); errormes("Не удалось подготовить буфер для записи!\n", wvres);
        wvres =  waveInAddBuffer(hwvindev, &wvbuf, sizeof(wvbuf));     errormes("Не удалось добавить буфер для записи!\n", wvres);
      } // for(UINT i = 0; i < nbuf; i++)
Помогите, пожалуйста, исправить ошибку. Прикрепляю проект.

MySound.zip (, : 373)

Автор: ilya.rakitin 15.07.11, 10:14
Привет, всем!
Решил не создавать новой темы, надеюсь, пишу в нужную ветку.
На работе попросили разобраться с интерфейсом в mmystem. Вроде всё просто и понятно, но тут возникла сложность с регулировкой уровня звука и баланса:
-используя самый простой метод, получаю доступ к первому попавшемуся destination, имеющий опцию контроля над уровнем громкости ("Общей громкости", в данном случае):

...
if (mixerGetNumDevs() < 1)
{
return;
}

unsigned int rc;

HMIXER hMix;
if (mixerOpen(&hMix, 0, 0, 0, 0) != MMSYSERR_NOERROR) {
return;
}

MIXERLINE ml = {0};
ml.cbStruct = sizeof(MIXERLINE);
ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
if( (rc=mixerGetLineInfo(reinterpret_cast<HMIXEROBJ>(hMix), &ml, MIXER_GETLINEINFOF_DESTINATION)) != MMSYSERR_NOERROR)
return;

MIXERLINECONTROLS mlc = {0};

MIXERCONTROL mc = {0};

mlc.cbStruct = sizeof(MIXERLINECONTROLS);
mlc.dwLineID = ml.dwLineID;
mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mlc.cControls = 1;
mlc.pamxctrl = &mc;
mlc.cbmxctrl = sizeof(MIXERCONTROL);

if( (rc=mixerGetLineControls(reinterpret_cast<HMIXEROBJ>(hMix), &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE)) != MMSYSERR_NOERROR)
return;

- дальше начинаются непонимания: в инициализации структуры MIXERCONTROLDETAILS для получения данных я не могу понять, какой флаг указать, чтобы мне выдали информацию в структуре MIXERCONTROLDETAILS_UNSIGNED о уровне баланса? Хорошо, решил проэкспериментировать, заполнил объект MIXERCONTROLDETAILS следующим образом:

...
MIXERCONTROLDETAILS_UNSIGNED* mxBalControlData = new MIXERCONTROLDETAILS_UNSIGNED[ml.cChannels*2];

MIXERCONTROLDETAILS mcd = {0};
mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
mcd.hwndOwner = 0;
mcd.cMultipleItems = 0;
mcd.dwControlID = mc.dwControlID;
mcd.paDetails = mxBalControlData;
mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
mcd.cChannels = ml.cChannels;

if((rc=mixerGetControlDetails(reinterpret_cast<HMIXEROBJ>(hMix), &mcd, MIXER_GETCONTROLDETAILSF_VALUE)) != MMSYSERR_NOERROR)
return;
...

Конечно, я проверил в отладчике, что ml.cChannels равна 2, а значит, как я понимаю, имеется доступ к управлению громкости и баланса, ок. Но никак не пойму до конца, что за значения мне приходят в mxBalControlData: должны прийти показатели или уровня громкости, или баланса, - поэкперементировав чуток, пришёл к выводу, что приходят данные с того ползунка, показатели которого последний раз изменяли. И как же мне указать, что в данный момент мне нужен control показателя баланса или показателя громкости?

Автор: Prince 15.07.11, 22:19
Размер данных контрола должен быть 8 байт: 4 байта на канал(?);
Я сделал так:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // volume:array[0..1] of integer;
    // volume[0] - левый канал
    // volume[1] - правый канал
    mxcdetail.cbDetails:=sizeof(MIXERCONTROLDETAILS_UNSIGNED)*Length(volume);
    mxcdetail.paDetails:=@volume;
     
    mixerGetControlDetails(mixercombobox.ItemIndex,@mxcdetail,
    MIXER_OBJECTF_MIXER or MIXER_GETCONTROLDETAILSF_VALUE);


Как вариант, если не нужен именно master volume, достаточно waveoutsetvolume c предварительной проверкой флага WAVECAPS_LRVOLUME в waveoutgetdevcaps. .


Контрол MIXERCONTROL_CONTROLTYPE_PAN пока не встречался.

Автор: ilya.rakitin 18.07.11, 08:27
ту Prince
А кто из них кто? Левый канал, правый канал? Где будут показатели с уровня громкости, а где с уровня баланса?

Автор: Prince 18.07.11, 13:14
Цитата
А кто из них кто? Левый канал, правый канал?

Ну, чисто интутивно, первое 4-байтовое - левый канал, второе - правый. Но а кто мешает проверить? :rolleyes:
Цитата
где будут показатели с уровня громкости, а где с уровня баланса?

У вас в наличии два регулятора громкости, раздельно по левому и правому каналам, с пределами регулировки от 0 до 65535.
А вам очень хочется общий регулятор громкости и баланса.
Можно поступить так.
Пределы регулировки громкости общего регулятора оставляете от 0 до 65535.
А баланса от -100 до +100.
-100 соотвествует левому крайнему положению слайдера баланса, +100 - крайнему правому.
И считаете громкость в каналах при изменении положения регуляторов vol или bal(надеюсь, не напутал):
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    case bal of
    -100..-1:Begin
             volume[0]:=vol;
             volume[1]:=trunc(vol*((100-abs(bal))/100);
             end;
    0:       Begin
             volume[0]:=vol;
             volume[1]:=vol;
             end;
    1..100:  Begin
             volume[0]:=trunc(vol*(100-bal)/100);
             volume[1]:=vol;
             end;
    end;

Дальше передаете расчитанные значения в mixerSetControlDetails. Зависимость громкость/баланс можно придумать и другую, например, такую, чтобы при изменении баланса громкость изменялась синхронно в обоих каналах, все в ваших руках. А если вдруг в каком-нибудь из кодеков обнаружите контрол типа MIXERCONTROL_CONTROLTYPE_PAN, дайте знать, интересно.

Все сказанное относится не только к линии master volume, но и к прочим.

Автор: ilya.rakitin 18.07.11, 13:30
Цитата Prince @
Цитата
А кто из них кто? Левый канал, правый канал?

Ну, чисто интутивно, первое 4-байтовое - левый канал, второе - правый. Но а кто мешает проверить? :rolleyes:
Цитата
где будут показатели с уровня громкости, а где с уровня баланса?

У вас в наличии два регулятора громкости, раздельно по левому и правому каналам, с пределами регулировки от 0 до 65535.
А вам очень хочется общий регулятор громкости и баланса.
Можно поступить так.
Пределы регулировки громкости общего регулятора оставляете от 0 до 65535.
А баланса от -100 до +100.
-100 соотвествует левому крайнему положению слайдера баланса, +100 - крайнему правому.
И считаете громкость в каналах при изменении положения регуляторов vol или bal(надеюсь, не напутал):
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    case bal of
    -100..1: Begin
             volume[0]:=vol;
             volume[1]:=trunc(vol*((100-abs(bal))/100);
             end;
    0:       Begin
             volume[0]:=vol;
             volume[1]:=vol;
             end;
    1..100:  Begin
             volume[0]:=trunc(vol*(100-bal)/100);
             volume[1]:=vol;
             end;
    end;

Дальше передаете расчитанные значения в mixerSetControlDetails. Зависимость громкость/баланс можно придумать и другую, например, такую, чтобы при изменении баланса громкость изменялась синхронно в обоих каналах, все в ваших руках. А если вдруг в каком-нибудь из кодеков обнаружите контрол типа MIXERCONTROL_CONTROLTYPE_PAN, дайте знать, интересно.

Все сказанное относится не только к линии master volume, но и к прочим.

Ту Prince
Т.е. если в массиве из двух элементов левому каналу соответсвует нулевой элемент, а правому - первый. То при заполнении данного массива функцией mixerGetControlDetails, как отличить какой элемент массива указывает на показания громкости, а какой на показания баланса? Будет ли нулевой элемент - уровнем громкости, а первый - уровнем баланса, как определяется порядок заполнения?

Автор: Prince 18.07.11, 13:54
Нету баланса, не-ту. Есть два канала громкости, левый/правый, каждому из них где-то во внутренностях аудиокодека(микросхема такая) соотвествуют некие регистры. А "баланс" вы высчитываете самоятоятельно из значений громкости каналов, возвращаемых драйвером. И наоборот, управляя балансом из своей программы, вы по значению "громкости" и "баланса" высчитываете значения громкости для каждого канала и передаете драйверу.

Посмотрите, сколько контролов имеет линия MIXERLINE_COMPONENTTYPE_DST_SPEAKERS и проверьте тип каждого контрола. С какой вы звуковушкой работаете?

Автор: ilya.rakitin 18.07.11, 14:11
Цитата Prince @
Нету баланса, не-ту. Есть два канала громкости, левый/правый, каждому из них где-то во внутренностях аудиокодека(микросхема такая) соотвествуют некие регистры. А "баланс" вы высчитываете самоятоятельно из значений громкости каналов, возвращаемых драйвером. И наоборот, управляя балансом из своей программы, вы по значению "громкости" и "баланса" высчитываете значения громкости для каждого канала и передаете драйверу.

Посмотрите, сколько контролов имеет линия MIXERLINE_COMPONENTTYPE_DST_SPEAKERS и проверьте тип каждого контрола. С какой вы звуковушкой работаете?

Ааа) Понятно. А я всё думал :wall: , что они отдельно нам выдают уже суммарные данные по балансу, и по уровню громкости для двух каналов, а оно вон оно как. Ясно.
А MIXERCONTROL_CONTROLTYPE_PAN у меня тоже не нашёл.
Спасибо.

Автор: IvanB 04.04.13, 11:44
Здравствуйте.
Пишу программу обработки сигналов, сигналы поступают на вход звуковой карты. Исходный код взят на просторах инета, думаю все кто начинал пользовать MMSystem его видели. Возникла следующая проблема: при получении уведомления запускается процедура обработки сигнала (начинаем анализировать данные буфера). Но в большинстве случаев начало буфера уже затерто новой порцией данных. Т.е. я полагаю что по достижении конца буфера драйвер (или что там пишет в него) посылает процедуре TForm1.OnWaveIn сообщение о заполнености буфера, переводит указатель на начало буфера и начинает писать свежие данные поверх старых.
Скрин прилагаю. "Стык" может быть в любом месте, но обычно это происходит в начале буфера.
rewrite.jpg (, : 872)

код Дельфи:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure TForm1.StartButtonClick(Sender: TObject);
    var header: TWaveFormatEx;
        BufLen: integer;
        buf: pointer;
    begin
      with header do begin
                      wFormatTag := WAVE_FORMAT_PCM;
                      nChannels := 2;          
                      nSamplesPerSec := 22050;
                      wBitsPerSample := 16;
                      nBlockAlign := nChannels * (wBitsPerSample div 8);
                      nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
                      cbSize := 0;
                     end;
     
      WaveInOpen(Addr(WaveIn), WAVE_MAPPER, addr(header),Form1.Handle, 0, CALLBACK_WINDOW);
      BufLen := header.nBlockAlign * BufSize;
      hBuf := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, BufLen);
      Buf := GlobalLock(hBuf);
      with BufHead do begin
                       lpData := Buf;
                       dwBufferLength := BufLen;
                       dwFlags := WHDR_DONE;// изначально было WHDR_BEGINLOOP;
                       dwLoops:=0;
                      end;
      WaveInPrepareHeader(WaveIn, Addr(BufHead), sizeof(BufHead));
      WaveInAddBuffer(WaveIn, addr(BufHead), sizeof(BufHead));
      GetMem(pRight,BufSize * sizeof(TPoint));
      GetMem(pLeft, BufSize * sizeof(TPoint));
      Work := true;
      WaveInStart(WaveIn);
    end;


сама процедура, которую вызываем
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure TForm1.OnWaveIn;
    var
      dSync,dBal,i,n: integer;
      data16: PData16;
      begin
      data16 := PData16(PWaveHdr(Msg.lParam)^.lpData);
      //всякие преобразования сигнала
      //If mono.Checked    then for i:=0 to BufSize_1 do data16^[i*2+1]:=data16^[i*2];
      //If Invert1.Checked then for i:=0 to BufSize_1 do data16^[i*2]:=-data16^[i*2];    //round(data16^[i*2]*(0.54-0.46*cos(2*Pi*I/BufSize)));
      //If Invert2.Checked then for i:=0 to BufSize_1 do data16^[i*2+1]:=-data16^[i*2+1];
      //..
      with PaintBox1.Canvas do
     begin
        Brush.Color := clWhite;
        FillRect(ClipRect);
        n:=(PaintBox1.Height div 2)+OffsetRCh.Position*2;
        
        for i:=0 to BufSize_1 do pRight^[i]:=Point(round(i*XScale),round(n-data16^[i*2+1]*YScale/YRCh.Position));
        Polyline(Slice(pRight^, BufSize);
        
        n:=(PaintBox1.Height div 2)+OffsetLCh.Position*2;
        for i:=0 to BufSize_1 do pLeft^[i-StartN]:=Point(round(i*XScale),round(n-data16^[i*2]*YScale));
        Polyline(Slice(pLeft^, BufSize);
     end;
     if Work then WaveInAddBuffer(WaveIn, PWaveHdr(Msg.lParam),SizeOf(TWaveHdr))
             else Work := true;
    end;


Вопрос - как запретить драйверу перезапись данных в буфере? Флаг dwLoops установил в 0 dwLoops:=0

Cпасибо.

Автор: IvanB 04.04.13, 11:51
И да, ответ на возможный вопрос об изменении сигнала внутри процедуры TForm1.OnWaveIn - убирал все действия, кроме вывода на экран. Не помогает, стык все равно где-нибудь да есть.

Автор: Prince 04.04.13, 17:37
Для обеспечения непрерывности потока данных необходимо использовать как минимум 2 буфера. Концепция waveform-audio предполагает очередь буферов. Почитайте темы пользователя tumanovalex, касающиеся работы с функциями mmsystem, в этом же разделе(а-а, эта одна из них :lol: ).
Цитата
Т.е. я полагаю что по достижении конца буфера драйвер (или что там пишет в него) посылает процедуре TForm1.OnWaveIn сообщение о заполнености буфера, переводит указатель на начало буфера и начинает писать свежие данные поверх старых.

"Виноват" не драйвер . Это Вы передаёте драйверу, в TForm1.OnWaveIn, только что заполненный буфер, а драйвер, естественно, снова начинает его заполнять, так как в очереди других буферов нет.
А кроме того, что данные перезаписываются, они ещё и теряются, поскольку обработка сообщения системой, и собственно выполнение содержимого TForm1.OnWaveIn (до WaveInAddBuffer) занимают какое-то время.

Добавлено
dwLoops - для зацикливания буфера при воспроизведении. При записи, если не ошибаюсь, содержимое dwLoops не обрабатывается вообще.

Обрабатывать буфер в оконной процедуре не совсем хорошо, лучше использовать механизмы уведомления CALLBACK_EVENT (в отдельном потоке), CALLBACK_FUNCTION, или CALLBACK_THREAD.

Автор: IvanB 05.04.13, 04:57
Ок. Это сигнал с балансировочного станка, в одном канале датчики баланса, в другом - датчик синхронизации (фотодатчик). В непрерывности потока данных нужды нет никакой, потому использую один буфер. Требуется получить массив с неискаженными данными (т.е. наложение сигнала внутри буфера недопустимо), обработать его (вычислить сдвиг фазы), и только после этого получить следующий кусок данных.

Цитата
"Виноват" не драйвер . Это Вы передаёте драйверу, в TForm1.OnWaveIn, только что заполненный буфер, а драйвер, естественно, снова начинает его заполнять, так как в очереди других буферов нет.

Так буфер передается драйверу после процедуры рисования (которая выводит УЖЕ искаженный буфер). Или это не так?

Цитата
Обрабатывать буфер в оконной процедуре не совсем хорошо, лучше использовать механизмы уведомления CALLBACK_EVENT (в отдельном потоке), CALLBACK_FUNCTION, или CALLBACK_THREAD.

к сожалению с потоками вообще не знаком :whistle: Программирую на уровне "Хэллоу ворлд!" Можно ли в этом варианте написания программы "попросить" драйвер остановить запись в буфер при вызове OnWaveIn?
Спасибо.

Автор: IvanB 05.04.13, 05:06
Ок. Это сигнал с балансировочного станка, в одном канале датчики баланса, в другом - датчик синхронизации (фотодатчик). В непрерывности потока данных нужды нет никакой, потому использую один буфер. Требуется получить массив с неискаженными данными (т.е. наложение сигнала внутри буфера недопустимо), обработать его (вычислить сдвиг фазы), и только после этого получить следующий кусок данных.

Цитата
"Виноват" не драйвер . Это Вы передаёте драйверу, в TForm1.OnWaveIn, только что заполненный буфер, а драйвер, естественно, снова начинает его заполнять, так как в очереди других буферов нет. А кроме того, что данные перезаписываются, они ещё и теряются, поскольку обработка сообщения системой, и собственно выполнение содержимого TForm1.OnWaveIn (до WaveInAddBuffer) занимают какое-то время.

Так буфер передается драйверу после процедуры рисования (которая выводит УЖЕ искаженный буфер). Или это не так? Только что пробовал в самом начале OnWaveIn вставить WaveInStop(WaveIn). Теперь стык появляется вообще в любом месте.

Цитата
Обрабатывать буфер в оконной процедуре не совсем хорошо, лучше использовать механизмы уведомления CALLBACK_EVENT (в отдельном потоке), CALLBACK_FUNCTION, или CALLBACK_THREAD.

к сожалению с потоками вообще не знаком :whistle: Программирую на уровне "Хэллоу ворлд!" Можно ли в этом варианте написания программы "попросить" драйвер остановить запись в буфер при вызове OnWaveIn? Или сделать хоть что-нибудь, чтоб получить массив из 1024-16000 неискаженных точек.
Спасибо.

Автор: IvanB 05.04.13, 07:52
Цитата
Для обеспечения непрерывности потока данных необходимо использовать как минимум 2 буфера. Концепция waveform-audio предполагает очередь буферов.

нашел в сети вариант работы с 2мя буферами. Программа пишет данные со звуковой карты в файл.
@wh1 и @wh2 - ссылки на буфера, при каждом вызове функции меняются между собой. Вроде красиво. Переделал под себя - не работает как надо. Видимо мой обработчик довольно долгий (выполняется преобразование Фурье, поиск в каждом канале сигнала "пиков", расчет фаз и вывод на экран) и буфер все равно успевает переполняться.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // обработчик "данные готовы"
    procedure TformMain.MMProcData(var Message: TMessage);
    var
       temp: pWaveHdr;
       recorded: integer;
    begin
       temp:=address;
       if address=@wh1 then address:=@wh2 else address:=@wh1;
    // если не остановлено, ставим в очередь
       if not stop then
            waveInAddBuffer(hwi,address,sizeof(TWaveHdr));
       recorded:=address.dwBytesRecorded;
    // записываем блок
       BlockWrite(fOut,(temp.lpData)^,recorded);
       n:=n+recorded;
       formMain.Label1.Caption:=IntToStr(n);
    end;

Автор: Prince 05.04.13, 07:54
Цитата
Или это не так?

В принципе, так.
Причину "затирания" можно искать и найти, например, в отладчике.
Или, сделайте копию буфера в начале OnWaveIn и сравните оригинал и копию перед вызовом waveinaddbuffer.
Цитата
Можно ли в этом варианте написания программы "попросить" драйвер остановить запись в буфер при вызове OnWaveIn? Или сделать хоть что-нибудь, чтоб получить массив из 1024-16000 неискаженных точек.
Спасибо.

Драйвер невиновен. Он выбрасывает из очереди заполненный буфер, уведомляет вас о том, что вот этот конкретный буфер уже заполнен, и вычёркивает его "из своей памяти".
Хоть что-нибудь можно. Найти ошибку, и исправить.
Цитата
dwFlags := WHDR_DONE

Флаги должны быть "по нулям". Флагами, как правило, распоряжается драйвер.

Добавлено
Цитата
нашел в сети вариант работы с 2мя буферами.

Да зачем вам вариант? В StartButtonClick добавьте ещё один буфер в очередь и всё.
Цитата
Видимо мой обработчик довольно долгий

Видимо, где-то ошибка.

Автор: IvanB 05.04.13, 08:20
Цитата
Или, сделайте копию буфера в начале OnWaveIn и сравните оригинал и копию перед вызовом waveinaddbuffer.

сделал и сравнил. Буфер в это время не меняется, даже sleep(100) вставлял в процедуру.

Автор: Prince 05.04.13, 08:48
Вывод: в буфере ничего не "затирается".

Автор: IvanB 05.04.13, 09:10
Переделал свою прг как вы советуете (добавил 2й буфер). Теперь стыков гораздо меньше, но они все равно проскакивают. Пока эту проблему решу анализом массива. Если стык есть - просто выюрасываем этот буфер и ждем следующего.
Спасибо!

Автор: Prince 05.04.13, 09:19
Цитата
Теперь стыков гораздо меньше, но они все равно проскакивают.

Запишите сигнал в звуковом редакторе. Если в нём присуствуют "стыки", значит, на входе звуковой карты такой сигнал.
Вставьте в OnwaveIn запись буферов в файл, и таким способом запишите звук из своей программы. Проанализируйте запись в том же редакторе, сравните.
Лучше найти причину глюка, и если она програмная, переписать программу по-человечески, чем костыли для игнорирования глюка изобретать. Имхо.

Автор: Prince 05.04.13, 10:20
Длительность буфера какая?

Автор: IvanB 05.04.13, 10:48
Ок. Сигнал был записан с помощью стандартной программы виндов "звукозапись", воспроизводилась через Винамп. Чтоб исключить возможную порчу звука Винампом сделано как вы советуете.
Из OnWaveIn выброшено все, добавлена только запись в файл. Используем 2 буфера, звук подаем на вход микрофона, я в него дышал, чтоб низкую частоту получить)
После быстренько накалякал программу чтения из ВАВа.
Читаем то, что записали в файл - Оппа, стыки идут!
test.jpg (, : 696)

Автор: IvanB 05.04.13, 10:51
Длительность (длина буфера) - 1024точки. Пробовал 2048 и т.д. до 16000 - не меняется ничего. Стык то есть, то нет. И если есть - то он обычно в начале буфера, со смещением 50-200 точек.
Частоту дискретизации ставил от 4000 до 44100.

Автор: Prince 05.04.13, 11:40
Из опыта, длительность буфера должна быть не менее 100 - 200 мс, иначе будут потери в данных.
wavein.rar (, : 330)

Автор: andriano 05.04.13, 14:47
Цитата IvanB @
Переделал под себя - не работает как надо. Видимо мой обработчик довольно долгий (выполняется преобразование Фурье, поиск в каждом канале сигнала "пиков", расчет фаз и вывод на экран) и буфер все равно успевает переполняться.

А не нужно все это делать в обработчике. Тем более, что сами пишете - непрерывный поток не нужен.
Обработчик должен схватить буфер и либо скопировать его, скажем, в закольцованную очередь, либо выяснить, готов ли его кто обрабатывать, и если не готов - игнорировать.

А программа обработки, соответственно, должна либо искать последний готовый буфер в очереди, освобождая все прочие в первом варианте, либо по завершении обработки выставлять флаг готовности и ждать, пока обработчик выплюнет очередной буфер.

Автор: Prince 05.04.13, 15:04
Цитата
Обработчик должен схватить буфер и либо скопировать его, скажем, в закольцованную очередь, либо выяснить, готов ли его кто обрабатывать, и если не готов - игнорировать.

Зачем ещё одна очередь, если она уже есть.

Автор: IvanB 05.04.13, 17:33
andriano, так буфер в обработчике изначально находится в непотребном состоянии, с искажениями.
Только что проверил свою программу дома, на стационарном компе. Все работает как надо! Нет никаких наложений даже с одним буфером, при любой его длине (выставлял даже 256точек, 44100 дискретность).
Но не работает нормально на ноутбуке на работе и на ноутбуке в гараже (в гараже старенький Тошиба, на работе свежий Асер). Такие вот пироги :blink:

Автор: andriano 06.04.13, 12:34
IvanB, вопрос, вероятно, не в том, стационарный комп или ноутбук, а в том, какой стоит звуковой чип и, соответственно, его возможности - что он делает аппаратно, а что перекладывает на процессор для программной обработки.

Любая правильно написанная программа надежно работает на любом железе. Если с Вашей программой этого не происходит, значит, она написана неправильно, а то, что она работает, говорит о том, что "железо" стационарного компа исправляет Ваши ошибки.
Следовательно, Вы что-то изначально неверно организуете, и Вам нужно не удивляться, почему программа, которая кое-где работает, работает не везде, а исправлять ее так, чтобы она работала на наиболее неблагоприятной для нее конфигурации.

"буфер в обработчике изначально находится в непотребном состоянии, с искажениями" - это неверная посылка. Если буфер у Вас оказывается в таком состоянии, значит, где-то Вы сами его портите. Либо что-то неправильное делаете с системой, что заставляет ее портить Ваш буфер.

Добавлено
Цитата Prince @
Зачем ещё одна очередь, если она уже есть.

Затем, что вторая очередь не будет совпадать с первой.
Они будут по-разному обрабатываться и их элементы будут по-разному освобождаться.

Автор: Prince 06.04.13, 12:46
Цитата
Затем, что вторая очередь не будет совпадать с первой.
Они будут по-разному обрабатываться и их элементы будут по-разному освобождаться.

Так зачем это нужно? Чем не устривает та очередь, что уже есть?

Автор: Prince 07.04.13, 07:07
Цитата
выставлял даже 256точек, 44100 дискретность

Выставьте 256 точек(6 мс), 1024(23 мс), 2048(46 мс) и 4096(92 мс) и сравните загрузку процессора. Допустите, что ваша программа выполняется на старенькой машине, параллельно с другими задачами, тоже требующими процессорного времени.

Автор: IvanB 08.04.13, 07:40
Prince, исправил в своей программе "инициализацию" буферов, что-то связанное с выделением памяти. Сделал как у вас в WaveIn.
До этого было так
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    type BufHead1,BufHead2: TWaveHdr;
         adress:PWaveHdr;
         header: TWaveFormatEx;
         buf: pointer;
     
    СтартБаттонКлик
    begin
     with header do begin
                      wFormatTag := WAVE_FORMAT_PCM;
                      nChannels := 2;  { количество каналов }
                      nSamplesPerSec := SpS; { частота }
                      wBitsPerSample := 16; { 8 / 16 бит }
                      nBlockAlign := nChannels * (wBitsPerSample div 8);
                      nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
                      cbSize := 0;
                     end;
     
      WaveInOpen(Addr(WaveIn), WAVE_MAPPER, addr(header),Form1.Handle, 0, CALLBACK_WINDOW);
      BufLen := header.nBlockAlign * BufSize;
      hBuf := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, BufLen);
      Buf := GlobalLock(hBuf);
      with BufHead1 do begin
                        lpData := Buf;
                        dwBufferLength := BufLen;
                        dwFlags := 0;//WHDR_DONE;//BEGINLOOP;
                        dwLoops:=0;
                      end;
      adress:=@BufHead1;
      with BufHead2 do begin
                        lpData := Buf;
                        dwBufferLength := BufLen;
                        dwFlags := 0;//WHDR_DONE;//BEGINLOOP;
                        dwLoops:=0;
                      end;
      WaveInPrepareHeader(WaveIn, Addr(BufHead1), sizeof(TWaveHdr));
      WaveInPrepareHeader(WaveIn, Addr(BufHead2), sizeof(TWaveHdr));
      WaveInAddBuffer(WaveIn, adress, sizeof(BufHead1));
      WaveInStart(WaveIn);


Переделал в такое
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    with header do begin
                      wFormatTag := WAVE_FORMAT_PCM;
                      nChannels := 2;
                      nSamplesPerSec := SpS;      
                      wBitsPerSample := 16;                
                      nBlockAlign := nChannels * (wBitsPerSample div 8);
                      nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
                      cbSize := 0;
                     end;
     
    WaveInOpen(Addr(WaveIn), WAVE_MAPPER, addr(header),Form1.Handle, 0, CALLBACK_WINDOW);
    BufLen := header.nBlockAlign * BufSize;
     
    adress:=allocmem(sizeof(TWAVEHDR));
    adress.lpData := allocmem(BufLen);
    adress.dwBufferLength := BufLen;
    WaveInPrepareHeader(WaveIn, adress, sizeof(TWaveHdr));
    WaveInAddBuffer(WaveIn, adress, sizeof(TWaveHdr));
     
    adress:=allocmem(sizeof(TWAVEHDR));
    adress.lpData := allocmem(BufLen);
    adress.dwBufferLength := BufLen;
    WaveInPrepareHeader(WaveIn, adress, sizeof(TWaveHdr));
    WaveInAddBuffer(WaveIn, adress, sizeof(TWaveHdr));
     
    WaveInStart(WaveIn);


Стало работать без ошибок! :good: Вечером еще на старом буке надо проверить.
Выделение буферов сейчас сделано как у вас в программе, здесь просто приведен пример того, что исправлено.

Спасибо!
зы: пока писал ответ, помоему понял ошибку. В первом случае я 1 раз выделил память (строки 21-22) и дальше писал по одному и тому же адресу 2 буфера.

Автор: IvanB 13.05.16, 06:32
Доброго!
Писать звук с 2х каналов научили :good: . Теперь хотелки выросли и нужно записать 4 канала на том же железе.
Имеем встроенную звуковую систему АС97 на чипе Alc201, т.е. 2х канальное стерео. АЦП имеет частоту дискретизации 48кГц и 4 пары каналов записи. Задача - получить 4х канальный сигнал с полезной частотой не превышающей 3кГц, синхронизированный во времени.
Идея следующая. Запрограммировать длину буфера весьма короткой (1, 2 или 4 отсчета). Получить от АЦП выборку 1й пары каналов, скинуть ее в буфер1. Потом переключить запись на другую пару каналов и получить выборку с нее, сбросить ее в буфер2. Получится ли таким образом записать "одновременно" 4 канала? Или потрачу много времени на переключение каналов?

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)