#include #include #include #include "ringbuffer.h" #include "MacMain.h" #include "SequenceGrabber.h" // NB: I assume that only one SequenceGrabber object is going to be created // The data proc should probably be specialized for the actual // transformation(s) needed. EXTERN_API(OSErr) myDataProc(SGChannel, Ptr, long, long*, long, TimeValue, short, long); enum { kNone, kLeft, kRight, kSum, k0Left, k0Right, kBoth }; namespace SequenceGrabber { SeqGrabComponent m_seqGrab; SGChannel m_audiochannel; SGChannel m_videochannel; SGDataUPP m_upp; bool m_haveloaded; bool m_running; bool m_paused; // the Audio parameters short m_numChannels; short m_sampleSize; Fixed m_sampleRate; // Not all hardware can change the sample size and number of channels. // And irritatingly enough, QuickTime is inconsistent about telling you // this. Fortunately, these are easy enough to do in software. (Let's // just hope that no hardware demands to do compression for us...) // stereofix values: first four are stereo-to-mono adjustments, the // last three are mono-to-stereo (and are unimplemented) int m_stereofix; // samplesize fixing is not yet implemented int m_samplefix; // 0 means leave alone, 1 means fix // Fortunately, I have verified that the sequence grabber *will* do // software rate conversion. // this points to the ring buffer when recording and is 0 otherwise JRingBuffer *m_audioRingBuffer; // The Video parameters // XXX }; void SequenceGrabberIdle(void) { using namespace SequenceGrabber; if (m_seqGrab != 0) SGIdle(m_seqGrab); } OSStatus SequenceGrabberInitialize(void) { return 0; } OSStatus SequenceGrabberRealInitialize(void) { OSErr err; using namespace SequenceGrabber; if (m_seqGrab != 0) return fBsyErr; // go away m_haveloaded = true; m_paused = false; m_running = false; // fire up QuickTime EnterMovies(); SeqGrabComponent seqGrab; seqGrab=OpenDefaultComponent(SeqGrabComponentType, 0); if (seqGrab == NULL) { return invalidComponentID; } err = SGInitialize(seqGrab); // tell it we're not making a movie if (err == 0) err = SGSetDataRef(seqGrab,0,0,seqGrabDontMakeMovie); if (err) { // just give up (void)CloseComponent(seqGrab); } else m_seqGrab = seqGrab; return err; } OSStatus SequenceGrabberTeardown(void) { OSErr err = noErr; using namespace SequenceGrabber; // it looks like CloseComponent will shut everything down for us (void)CloseComponent(m_seqGrab); m_seqGrab = 0; ExitMovies(); return err; } // Set up the audio channel OSStatus SequenceGrabberOpenRecorder(const char *name, const soundParams *params, unsigned long *handlep) { OSErr err; long buftime = (long)FixRatio(-1, 60); using namespace SequenceGrabber; if (!m_haveloaded) { err = SequenceGrabberRealInitialize(); if (err) return err; } // XXX verify not running if (m_audiochannel) return fBsyErr; if (m_running) { err = SGPause(m_seqGrab, seqGrabPause); m_paused = true; if (err) return err; } err = SGNewChannel(m_seqGrab, SoundMediaType, &m_audiochannel); if (err) return err; // set usage for new audio channel to avoid playthrough err = SGSetChannelUsage(m_audiochannel, seqGrabRecord ); // Set up the chunk size to compromise between latency and thread // scheduling irritations if (err == 0) err = SGSetSoundRecordChunkSize(m_audiochannel, buftime); if (err) { // tear down audio channel SGDisposeChannel(m_seqGrab,m_audiochannel); m_audiochannel = 0; } else { m_numChannels = params->sp_channels; m_sampleSize = params->sp_samplesize; m_sampleRate = params->sp_samplerate; *handlep = (unsigned long)m_audiochannel; } if (m_paused) { err = SGPause(m_seqGrab, seqGrabUnpause); m_paused = false; } return err; } OSStatus SequenceGrabberCloseRecorder(unsigned long /* handle */) { int err; using namespace SequenceGrabber; if (m_audiochannel) { fprintf(stderr,"closing recorder channel\n"); if (m_running && !m_paused) { if (m_videochannel) err = SGPause(m_seqGrab, seqGrabPause); else { err = SGStop(m_seqGrab); m_running = false; } } assert(err == 0); err = SGDisposeChannel(m_seqGrab,m_audiochannel); assert(err == 0); m_audiochannel = 0; if (m_audioRingBuffer) { m_audioRingBuffer->Shutdown(); m_audioRingBuffer = 0; } // resume capture? if (m_videochannel) { err = SGPause(m_seqGrab, seqGrabUnpause); } } return 0; } OSStatus SequenceGrabberRecorderParams(unsigned long /*h*/, const soundParams *sp) { using namespace SequenceGrabber; OSErr err = noErr; short origchans, origbits; OSType origcomp; // want to set 'raw ' SGChannel channel = m_audiochannel; do { // not a loop // find the current settings if ((err = SGGetSoundInputParameters(channel, &origbits, &origchans, &origcomp)) != noErr) break; // Try to set the number of channels m_stereofix = kNone; if (origchans != sp->sp_channels) { err = SGSetSoundInputParameters(channel, 0, sp->sp_channels, 0); if (err != noErr) { // if the err is notEnoughHardwareErr, then the hardware // (and driver) is unwilling to deliver mono/stereos. // We can (sort of) fix that easily enough. if (err == notEnoughHardwareErr) { // do we want mono or stereo? if (sp->sp_channels == 1) { // XXX always sum m_stereofix = kSum; } else { // XXX always output left channel m_stereofix = k0Right; } err = 0; } else break; } } m_numChannels = sp->sp_channels; // Here's a nasty bit: for samplesize and compression, QT appears to // return success when you try to change them but silently fails to // do so. m_samplefix = 0; if (origbits != sp->sp_samplesize) { err = SGSetSoundInputParameters(channel, sp->sp_samplesize, 0, 0); if (err != noErr && err != notEnoughHardwareErr) break; if (err == noErr) { // yes, but did it work? err = SGGetSoundInputParameters(channel, &origbits, &origchans, &origcomp); if (err) break; if (origbits != sp->sp_samplesize) err = notEnoughHardwareErr; // geez! } if (err == notEnoughHardwareErr) { assert(origbits == 8 || origbits == 16); if (origbits == 8) // and want 16 m_samplefix = 1; else m_samplefix = -1; // have 16, want 8 } } m_sampleSize = sp->sp_samplesize; // just make sure compression is off if (origcomp != kOffsetBinary) { // OK, here's a nauseating little treat for you: // GetSoundInputParameters returns 'raw ' for uncompressed samples. // That makes sense, right? Well, 'raw ' is supposedly // 8-bit offset binary (0..255); the uncompressed 16-bit format // is supposed to be 'twos' on a bigendian machine. However, // GetSoundInputParameters returns 'raw ' for all raw formats. // I guess this is one of them hysterical oddities. It makes // sense UNLESS you read the documentation... if ((err = SGSetSoundInputParameters(channel, 0, 0, kOffsetBinary)) != noErr) break; if ((err = SGGetSoundInputParameters(channel, 0, 0, &origcomp)) != noErr) break; assert(origcomp == kOffsetBinary); if (origcomp != kOffsetBinary) { // this should probably be "tooMuchSoftwareErr", but there // doesn't appear to be such an error code... return notEnoughHardwareErr; } } // Finally, change the sampling rate. Fortunately, that seems to // just work even if the harware would rather not. // SetSoundInputRate takes a Fixed value, which is a 16.16 fixed-point // number. This limits audio capture to 65KHz. m_sampleRate = Long2Fix(sp->sp_samplerate); if ((err = SGSetSoundInputRate(channel, m_sampleRate)) != noErr) break; // just assume it worked. } while (0); // end of "break scope" return err; } OSStatus SequenceGrabberStartRecorder(unsigned long /*h*/, JRingBuffer *rb) { OSErr err = 0; using namespace SequenceGrabber; // XXX Now here's an annoying problem: // XXX both the audio and video channels really should be set up before // XXX we get to this point. if (m_upp == 0) { // register the callback and fire it up // create our Universal Procedure Pointer m_upp = NewSGDataUPP(myDataProc); err = SGSetDataProc(m_seqGrab, m_upp, 0); if (err == 0) { m_running = true; err = SGStartRecord(m_seqGrab); } } // Now, the sequence grabber data proc is running; deftly and atomically // establish a place for audio data to go if (err == 0 && !OTCompareAndSwap32((UInt32)0, (UInt32)rb, (UInt32*)&m_audioRingBuffer)) { err = fBsyErr; } return err; } OSStatus SequenceGrabberStopRecorder(unsigned long /*h*/) { OSErr err = 0; using namespace SequenceGrabber; if (m_audiochannel) { fprintf(stderr,"Stopping recorder channel\n"); err = SGPause(m_seqGrab, seqGrabPause); m_paused = true; assert(err == 0); // I assume that by this point, the dataproc is not running if (m_audioRingBuffer) { m_audioRingBuffer = 0; } assert(err == 0); if (m_videochannel) { m_paused = false; err = SGPause(m_seqGrab, seqGrabUnpause); } } return 0; } OSErr AudioDataProc( Ptr p, long len, long *offset ); DEFINE_API(OSErr) myDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) { #pragma unused(offset,chRefCon,time,refCon) using namespace SequenceGrabber; ComponentResult err = noErr; if(writeType == seqGrabWriteReserve) return 0; // who cares? // OK, which channel is it? if (c == m_audiochannel) return AudioDataProc(p, len, offset); return err; } OSErr AudioDataProc( Ptr p, long len, long *offset) { using namespace SequenceGrabber; OSErr err = noErr; if (len == 0 || p == 0) return 0; if (m_stereofix != kNone) len /= 2; assert(m_samplefix == 0); // shovel the data into the buffer int tocopy = len; // grr. all the world's a vax, OK? unsigned char *buf = (unsigned char*)m_audioRingBuffer->ReserveToWrite(tocopy); assert(buf != 0); while (len > 0 && buf != 0) { int i; #ifndef NDEBUG if ((len&1) == 0) assert((tocopy&1) == 0); if (m_sampleSize == 16 && m_stereofix != kNone) assert((tocopy&1) == 0); #endif switch(m_stereofix) { case kNone: memmove(buf, (void *)p, tocopy); break; case kLeft: if (m_sampleSize == 16) { for (i = 0; i < tocopy;) { // take the left (first) channel buf[i++] = *p++; buf[i++] = *p++; p+=2; } } else { for (i = 0; i < tocopy; i++) { // take the left (first) channel buf[i] = *p++; p++; } } break; case kRight: if (m_sampleSize == 16) { for (i = 0; i < tocopy;) { // take the second (right) channel p += 2; buf[i++] = *p++; buf[i++] = *p++; } } else { for (i = 0; i < tocopy; i++) { // take the right (second) channel p++; buf[i] = *p++; } } break; case kSum: if (m_sampleSize == 16) { for (i = 0; i < tocopy;) { // sum both channels short s = (((short *)p)[0] / 2) + (((short *)p)[1] / 2); *(short *)buf = s; buf += 2; i += 2; p+=4; } } else { for (i = 0; i < tocopy; i++) { // sum the two channels buf[i] = ((signed char*)p)[0]/2 + ((signed char*)p)[1]/2; p+=2; } } break; } len -= tocopy; if (len) { int didcopy = tocopy; tocopy = len; buf = (unsigned char *)m_audioRingBuffer->CommitMore(didcopy, tocopy); assert(buf != 0); } else { (void)m_audioRingBuffer->CommitFinal(tocopy); } } return err; }