Newsgroup: it.comp.lang.c++
Titolo: Record Sound

Vorrei conoscere quale e' la procedura minima per costruire una
funzione che legga l'input dalla scheda sonora in windows 95.
Dato  che la vorrei inserire in un mio programma di acquisizione dati come
canale di test.
Le informazioni che ho reperito sono molto tortuose (la descrizione delle
api di windows è peggio),
a me servirebbe una funzione ridotta all'osso che legga l'input della scheda
sonora e restituisca il valore in uscita.
Qualcuno puo' aiutarmi o tantomeno indirizzarmi sulla strada giusta. (tipo
sequenza di funzioni win da chiamare)
GRAZIE Roberto


Per acquisire dei samples da una scheda sonora non é possibile farlo con
una singola funzione, l'utente può decidere quando iniziare il processo, ma
sarà poi il sistema a informarlo via via dell'avanzamento/termine
dell'assunzione dati.
La procedura é divisa in 3 parti principali:
1) Inizio della registrazione (Inizializzazione, apertura e start):
      Questa fase può essere per esempio innescata premendo un
      button "Record" e gestendo il relativo messaggio generato.
      - Determina quale audio-device vuoi aprire tramite waveInGetNumDevs,
        che ti ritornerà il numero di audio-input-device, e con la funzione
        waveInGetDevCaps che identifica di quale device si tratta.
        A questo punto sai che il valore degli ID degli audio-input-device
        del tuo sistema, sarà compreso tra 0 e waveInGetNumDevs() - 1.
        Puoi per esempio impostare un ComboBox con tutti i nomi dei
        tuoi audio-input-device ricavati con waveInGetDevCaps e utilizzarlo
        per selezionare l'input-device desiderato.
        Una scorciatoia a quanto detto sopra, é usare l'ID WAVE_MAPPER.
      - Apri un determinato audio-device (vedi ID del device del punto
        precedente) con waveInOpen dopo aver preventivamente inizializzato
        la struttura WAVEFORMATEX richiesta dalla funzione.
      - Usa waveInPrepareHeader per preparare un buffer di ricezione e in
        seguito annuncialo al device con waveInAddBuffer.
        Se usi più di un buffer nella coda di ricezione, puoi farlo usando
        più volte la coppia di funzioni appena citata.
        Il fatto di usare uno o più buffer di ricezione, dipende dal fatto
        se la lunghezza della "registrazione" é nota o meno, se lo é,
        allora puoi usare un buffer unico di grandezza uguale al numero di
        samples richiesti, in caso contrario, usa almeno 2 buffer di
        grandezza a tua discrezione per non avere dei "gaps"
        nell'acquisizione dati.
        ** La grandezza dei buffer idonea dovrebbe essere calcolata in base
            al formato audio usato e al tempo richiesto per "scaricare" i
            samples di un buffer ritornato mentre un altro é a disposizione
            della coda di ricezione.
      - Fai partire la "registrazione" con waveInStart.
2) Gestione dei samples in entrata:
      Ogni volta che un buffer di ricezione é stato riempito, l'input-device
      genera un "evento" che informa l'applicazione, questo "evento" dipende
      dal parametro fdwOpen di waveInOpen che determina il modo di apertura.
      Supponendo che hai usato CALLBACK_WINDOW, allora dovrai gestire
      almeno il messaggio MM_WIM_DATA che conterrà nel parametro
      lParam l'indirizzo della struttura WAVEHDR relativa al buffer in
      questione.
      Il pacchetto di samples ritornato é ora a tua disposizione e ne puoi
      fare quello che vuoi, salvarlo etc.
      Una volta utilizzati i dati ("scaricati"), il buffer in questione é
      pronto per essere di nuovo aggiunto alla coda di ricezione con
      waveInAddBuffer (non c'é più bisogno di waveInPrepareHeader).
      ** Finché vorrai acquisire dei samples, almeno un buffer deve essere
          presente nella coda di ricezione.
3) Fine dell'acquisizione dati (stop e chiusura):
      La procedura può terminare in 2 modi:
      - Manualmente; per esempio premendo un button "Stop record".
        In questo caso usa waveInReset per fermare l'acquisizione dati e
        ritornare il contenuto dei buffers nella coda di ricezione.
        Quando si chiama waveInReset, ogni buffer ancora nella coda di
        ricezione verrà ritornato all'applicazione tramite la fase 2), la
        struttura WAVEHDR indicherà il numero di samples contenuti
        (eventualmente 0, se il buffer non é stato utilizzato).
        E' appunto alla fine della fase 2) che waveInUnprepareHeader deve
        essere utilizzata per ogni buffer ritornato, una volta
        "dis-preparato" tutti i buffers, usa waveInClose per chiudere
        l'audio-input-device.
      - Automaticamente; questo é il caso in cui la durata della
        "registrazione" é nota, in questo caso, il tutto avverrà alla fine
        della fase 2).
        Quando, al momento di un ritorno di buffer alla fase 2), il numero
        richiesto di samples é raggiunto, allora chiama waveInReset,
        utilizza waveInUnprepareHeader per ogni buffer ritornato (sempre
        con la fase 2), questo é causato da waveInReset) e infine chiudi
        il device con waveInClose.
Come vedi non é proprio la cosa più semplice del mondo, comunque, in termini
pratici, é meno complicato di quanto possa sembrare.
Le specifiche dei parametri e i valori di ritorno delle funzioni le trovi
nell'Help-in-line.
Spero di essere stato il più chiaro possibile e non troppo prolisso.
Saluti, Neil

una singola funzione, l'utente può decidere quando iniziare il processo, ma
..............

Ringranziandoti ancora per i preziosi consigli, devo dirti che sono riuscito
ad implementare il tutto
l'unico punto ancora poco chiaro riguarda la funzione "waveInOpen " dove io
ho indicato una mia procedura che
si deve occupare della gestione dei messaggi, fin qui tutto bene, solo che
io non ho una durata fissa dell'acquisizione
quindi tutto deve avvenire in modo continuo, il messaggio "MM_WIM_DATA"  lo
processo e acquisisco i dati del bufffer
che ho precedentemente preparato, ma quale è il metodo migliore per rilevare
quando si tratta di piu' buffer (array) che
l'ultimo buffer sia stato processato e che quindi bisogna inserirne uno
nuovo in coda, inoltre penso che debba far gestire tutto questo
da un thread per evitare blocchi della procedura e in questo caso cosa
dichiaro in "waveInOpen" (e sopratutto come rilevo i messaggi nel thread)
, ho visto su alcuni sorgenti prelevati in rete che dichiarano nel parametro
fdwOpen quanto segue:
  waveInOpen (
        &_handle,
        idDev,
        &format,
        (DWORD) HANDLE (event),
        0, // callback instance data
        CALLBACK_EVENT);
e cioè  "CALLBACK_EVENT" una costante non documentata dal mio help del MS
VC++ 4.0 ma che viene accettata senza problemi
se provo a compilare il tutto, hai idea di cosa sia e come si usi.
Sperando di essere stato chiaro, ti saluto.. Roberto


Purtroppo l'ultima volta che ho usato questa procedura, era ancora sotto
Windows 3.1 ed a quel tempo esistevano solo CALLBACK_FUNCTION
e CALLBACK_WINDOW, quindi non ho mai sperimentato ne
CALLBACK_EVENT, ne CALLBACK_THREAD.
Comunque ho effettuato un piccolo test su CALLBACK_THREAD, e ho
visto che MM_WIM_DATA viene inviato alla main-window dell'applicazione.
Purtroppo però ho potuto intercettarlo solo con PreTranslateMessage, ma
in seguito questo messaggio non viene "propagato" perché pMsg->hwnd
in PreTranslateMessage é impostato con NULL.
Se ci tieni, puoi provare a gestire MM_WIM_DATA con PreTranslateMessage
benché mi sembri poco ortodosso.
Nel caso di CALLBACK_THREAD, il parametro dwCallback di waveInOpen
può essere impostato con GetCurrentThreadId().
Se qualcuno che legge questo ne sapesse di più ........
Per quanto riguarda CALLBACK_EVENT, presumo, e sottolineo presumo,
che dwCallback di waveInOpen debba essere impostato con un handle
ricavato da CreateEvent, vedi CEvent, CreateEvent OpenEvent e soci.
Più avanti proverò ad interessarmene a meno che qualcuno possa
illuminarmi in proposito.
Saluti, Neil

ricominciare a reinserirli nella coda, anzi, non deve mai esserci
un...........

Ho risolto, e adesso tutto funziona alla perfezione.
A dire il vero avrei bisogno ancora un tuo consiglio circa la lettura dei
valori in input nelle varie modalita', cioe' come e' strutturato il dato
presente in
ingresso (quello che prelevo con " lpwh->lpData ".
Io ho creato una funzione che riceve in ingresso lpData e restituisce in
uscita un valore, come segue:
int GetSample16mono (char *pBuf, int i)
    {
    return ((short *) pBuf)[i];
    }
Ritorno un int e ho una funzione per ogni formato
(8mono,16mono,8stereo,16stereo) e per quanto
riguarda la parte "mono" sembra che funzioni, ma per la parte "stereo" come
devo trattare i dati??
Ciao, Roberto.


Ciao , un po' in ritardo ho visto il tuo messaggio.
Come hai già potuto costatare tu stesso, i samples a 16
bits sono già in formato INTEL (byte-low e poi byte-high).
Se l'input é stereo, allora i samples sono interlacciati:
    - 8 bits stereo:
        byte canale 0 (left), poi byte canale 1 (right) e così via.
    - 16 bits stereo:
        short canale 0 (left), poi short canale 1 (right) e così via.
Di conseguenza, é necessario che l'ampiezza del buffer (lpwh->lpData)
sia multiplo della grandezza di un sample completo, per esempio nel
formato 16 bits stereo, un sample occupa 4 bytes:
    byte-low canale 0 (left)
    byte-high canale 0 (left)
    byte-low canale 1 (right)
    byte-high canale 1 (right)
La grandezza del buffer é specificata con lpwh->dwBufferLength.
Questo serve a non ritrovarti parte di un sample alla fine di un buffer
e il resto all'inizio di un altro buffer.
Piccolo esempio:
short GetSample16left( char *pBuf , int i )
{
    return ((short *) pBuf)[ i * 2 ];
}
short GetSample16right( char *pBuf , int i )
{
    return ((short *) pBuf)[ ( i * 2) + 1 ];
}
Inoltre potrà esserti utile sapere che nel formato 16 bits, il valore
del sample é rappresentato in complemento a due (spero tu sappia
cosa significa) che altro non é che un semplicissimo short con segno.
Nel caso invece di un sample a 8 bits, il valore é rappresentato da un
BYTE e cioè un char senza segno, di conseguenza il valore "audio" 0
sarà rappresentato dal valore di mid-point 128 nel BYTE in questione.
Riassunto:
    8 bits:
        Valore minimo = 0
        Valore medio (mid-point) = 128
        Valore massimo = 255
    16 bits:
        Valore minimo = -32768
        Valore medio (mid-point) = 0
        Valore massimo = 32767
Spero sia tutto chiaro, saluti , Neil

Se l'input é stereo, allora i samples sono
interlacciati:......................