This is 04_Rec_Mike.rtf in view mode; [Download] [Up]
Written by J. Laroche at the Center for Music Experiment at UCSD, San Diego California. December 1990. A more complex example: recording a sound from the microphone. Here's an example of a program setting-up a stream from the microphone to the memory, and recording through the stream. This example illustrates the way recording is done in the sound/dsp driver. This code can be found in Examples/02_Rec_Mike/perso_b.c // ----------------------- Beginning of program #import <sound/sound.h> #import <sound/sounddriver.h> #import <mach.h> #import <stdio.h> #define Error(A,B) if((A)) {fprintf(stderr,"%s %s\n",B, SNDSoundError((A)));\ mach_error(B,(A)); } #define DMASIZE 4096 #define READ_TAG 1 static int done; static char *read_data; static int read_count; static void recorded_data(void *arg, int tag, void *p, int nbytes) // This gets called when the entire result array has been read from SNDIN. { printf("Recording done... \n"); read_data = (char *)p; read_count = nbytes; done = 1; } static void read_started(void *arg, int tag) // This gets called when the driver starts reading SNDIN. { printf("Starting recording... \n"); } main (int argc, char *argv[]) { static port_t dev_port, owner_port; static port_t reply_port, read_port; int i, protocol; kern_return_t k_err; snddriver_handlers_t handlers = { 0, 0, read_started, 0, 0, 0, 0, 0, recorded_data}; msg_header_t *reply_msg; int low_water = 48*1024; int high_water = 64*1024; k_err = SNDAcquire(SND_ACCESS_IN,0,0,0, NULL_NEGOTIATION_FUN,0,&dev_port,&owner_port); Error(k_err,"SND acquisition "); k_err = snddriver_stream_setup(dev_port, owner_port, SNDDRIVER_STREAM_FROM_SNDIN, DMASIZE, 1, low_water, high_water, &protocol, &read_port); Error(k_err,"Stream set_up"); k_err = port_allocate(task_self(),&reply_port); k_err = snddriver_stream_start_reading(read_port,0,32000,READ_TAG, 1,0,0,0,0,0, reply_port); Error(k_err,"Starting reading "); reply_msg = (msg_header_t *)malloc(MSG_SIZE_MAX); done = 0; while (done != 1) { reply_msg->msg_size = MSG_SIZE_MAX; reply_msg->msg_local_port = reply_port; k_err = msg_receive(reply_msg, MSG_OPTION_NONE, 0); k_err = snddriver_reply_handler(reply_msg,&handlers); } // print a few received samples... for (i=5000; i<5020; i++) printf("sample[%d] = %d \n",i,read_data[i]); vm_deallocate(task_self(),(pointer_t)read_data,read_count); } /----------- End of program To compile this program, you would use cc -o myProgram -Wall myProgram.c -lsys_s INCLUDES, DEFINES and VARIABLES. We declared a new static function, recorded_data() which will be called by the driver when recording is completed (see below.) INITIALIZING THE DRIVER: Since we only need the microphone, we only ask for SND_ACCESS_IN in the SNDAcquire() function. The set-up call now is k_err = snddriver_stream_setup(dev_port, owner_port, SNDDRIVER_STREAM_FROM_SNDIN, DMASIZE, 1, low_water, high_water, &protocol, &read_port); SNDDRIVER_STREAM_FROM_SNDIN refers to a stream from the CODEC microphone to the memory. Note that the sampling size is now 1 (since the microphone only sends 1 byte samples coded according to the Mu_Law function.) STARTING RECORDING k_err = snddriver_stream_start_reading(read_port,0,32000,READ_TAG, 1,0,0,0,0,0, reply_port); enqueues a reading command on the stream identified by read_port. We want to record 32000 samples (about 4 seconds of sound), and we only ask for a started message to be sent to the previously allocated reply_port. At that point, the driver starts reading from the CODEC input (microphone), and therefore sends a "started" message to the reply_port. GETTING THE RECORDED SAMPLES When the driver reads from the stream, it puts the incoming samples in a buffer allocated internally. When it has finished doing it, it sends a "recorded_data" message to the reply_port you passed to the snddriver_stream_start_reading() function. You can then retrieve this message using the classical msg_receive() mach function, and handle it using the handler snddriver_handlers_t handlers = { 0, 0, read_started, 0, 0, 0, 0, 0, recorded_data}; The last field in the handler is a pointer to a function we provided: recorded_data(). This function is called when the message in snddriver_reply_handler(reply_msg,&handlers) is a "recorded_data" message. It must take four arguments: the first one corresponds to the one you passed in the handler (here, it's simply zero.), the tag is set to the tag of the region this message comes from, p is a pointer to the received samples, and nbytes gives the number of read bytes. static void recorded_data(void *arg, int tag, void *p, int nbytes) In other words, when it finishes reading, the driver sends a "recorded_data" message containing a pointer to the recorded data and the number of recorded bytes. This message is recognized by the snddriver_reply_handler() function which in turn calls the corresponding function in handlers (provided you gave one!). WARNING: You don't need to request "recorded_data" messages (like you needed for all other messages -started, pausing etc) when you call snddriver_stream_start_reading() but if you don't provide a function for "recorded_data" messages in the handler, you won't receive the samples. Since the driver has allocated a buffer for the incoming samples, once you're done with them, you should deallocate the buffer: vm_deallocate(task_self(),(pointer_t)read_data,read_count); This way, you're not wasting virtual memory. GETTING THE DATA BEFORE RECORDING FINISHED In this example, we knew we needed 32000 samples. If you want to get the data recorded so far without waiting for 32000 samples to be read, you can use the snddriver_stream_control() function with a SNDDRIVER_STREAM_AWAIT control option. This will cause the driver to send a "recorded_data" message containing all the samples read so far. When the driver finishes recording, it will send another "recorded_data" message with the rest of the recorded samples. WARNING: If you send a snddriver_stream_control() with an SNDDRIVER_STREAM_ABORT control option to a reading stream (like the current one), the driver will stop recording, and send TWO "recorded_data" messages (instead of just one): the first one containing the data recorded so far, the second, additional one, containing more data. This seems to be a bug, since you're interested only in the first message. The problem is that the second "recorded_data" message will stay on the reply_port's queue if you don't fetch it. Suppose you want to start recording another region (on the same stream or on another one) using the same reply_port, once recording is finished, you'll get a "recorded_data" message which will be placed on the reply_port's queue just after the junk one. If you try to retrieve the recorded samples on the reply_port, you'll get the junk old data instead of the newly recorded samples (which are in the next message!). A solution can be to use different reply ports for each recording (expensive and unefficient) or to detect that your recording has been aborted (for example requesting "aborted" messages in your call to snddriver_stream_start_reading()) and just dumpimg the second "recorded_data" message when you get the first (and good) one. This "bug" does not seem to have been fixed in the new release (2.0).
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.