/* $Id$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ///////////////////////////////////////////////////////////// // aRts interface #include #include "oggarts.h" #include #include #include "oggPlayObject_impl.h" using namespace Arts; oggPlayObject_impl::oggPlayObject_impl() { struct shmid_ds bleh; shm_id = shmget(IPC_PRIVATE, sizeof(*shm_buf), 0600); shm_buf = (struct buf_t *)shmat(shm_id, 0, 0); // mark it to be destroyed after the last detach shmctl(shm_id, IPC_RMID, &bleh); // sem0 has base // sem1 remaining space // sem2 seekTo buflen_sem = semget(IPC_PRIVATE, 4, 0600); child_pid = 0; } oggPlayObject_impl::~oggPlayObject_impl() { halt(); union semun foo; arts_debug("oggvorbis: removing IPC resources"); semctl(buflen_sem, 0, IPC_RMID, foo); } bool oggPlayObject_impl::loadMedia(const std::string &filename) { halt(); // stop playing any previous stream currentFile = filename; //throw off a process to handle the decoding //fill the buffers first... short decode_buf[BACKBUFSIZ]; struct sembuf semoper; semoper.sem_flg = 0; buf_pos = 0; // setup the initial semaphore joy union semun foo; foo.val = 0; if (semctl(buflen_sem, 0, SETVAL, foo)) arts_debug("oggvorbis: couldn't clear queue %d, %s", errno, strerror(errno)); // no data in the queue foo.val = 0; // seekTo is -1 (ie, no seek) if (semctl(buflen_sem, 2, SETVAL, foo)) arts_debug("oggvorbis: couldn't clear seekTo, %s",strerror(errno)); semctl(buflen_sem, 3, SETVAL, foo); arts_debug("oggvorbis: seekTo is %d", semctl(buflen_sem, 2, GETVAL, foo)); // setup the starting free space in the buffer foo.val = BACKBUFSIZ; if (semctl(buflen_sem, 1, SETVAL, foo)) arts_debug("oggvorbis: couldn't mark buffer empty"); FILE *ogg_fp; ogg_fp = fopen(filename.c_str(), "r"); int current_section=0; ov_open(ogg_fp, &vf, NULL, 0); if (!(child_pid = fork())) { arts_debug("oggvorbis: child process"); do { // get more data int seekTo = semctl(buflen_sem, 2, GETVAL, foo); if (seekTo) { arts_debug("oggvorbis: seeking to %d", seekTo); // we have a seek command int ret = ov_time_seek(&vf, (double)seekTo-1); arts_debug("oggvorbis: ov_time_seek returned: %d\n", ret); foo.val=0; semctl(buflen_sem, 2, SETVAL, foo); // we've done it } foo.val=(long)ov_time_tell(&vf); if (foo.val == -1) foo.val=0; semctl(buflen_sem, 3, SETVAL, foo); int thisPass = ov_read(&vf, (char *)decode_buf, BACKBUFSIZ*sizeof(short), 0, 2, 1, ¤t_section); if (thisPass == 0) { // we're done, or we errored (in which case we're done) break; } thisPass = (thisPass / 4); semoper.sem_num = 1; semoper.sem_op = -thisPass; semop(buflen_sem, &semoper, 1); // block until there's enough space to stick in this frame int roomFor = semctl(buflen_sem, 1, GETVAL, foo); if (roomFor > BACKBUFSIZ) { arts_debug("oggvorbis: exit requested, bye!"); // this can never go above BACKBUFSIZ in normal operation, // the consumer wants us to exit break; } for (int i=0 ; i left[buf_pos] = conv_16le_float(decode_buf[2*i]); shm_buf->right[buf_pos] = conv_16le_float(decode_buf[2*i+1]); } //arts_debug("oggvorbis: enqueued them"); semoper.sem_num = 0; semoper.sem_op = thisPass; semop(buflen_sem, &semoper, 1); // mark the additional data now available //arts_debug("oggvorbis: calculated %d more samples",shm_buf->back$ } while(1); //signal completion foo.val = 0; // no more data available semctl(buflen_sem, 0, SETVAL, foo); // and no room either (ie, none coming) semctl(buflen_sem, 1, SETVAL, foo); arts_debug("oggvorbis: decoder process exiting"); exit(0); } return true; } std::string oggPlayObject_impl::description() { return "ogg/vorbis artsplugin - this is my w00t!"; } poTime oggPlayObject_impl::currentTime() { poTime time; union semun foo; foo.val=0; time.seconds = semctl(buflen_sem, 3, GETVAL, foo); if (time.seconds == -1) time.seconds = 0; // Eek infinite loop time. time.ms=0; //arts_debug("oggvorbis: time is now %d\n", time.seconds); return time; } poTime oggPlayObject_impl::overallTime() { poTime time; time.seconds=(long)ov_time_total(&vf, -1); time.ms=0; return time; } poCapabilities oggPlayObject_impl::capabilities() { return static_cast(capPause|capSeek); } std::string oggPlayObject_impl::mediaName() { return currentFile; } poState oggPlayObject_impl::state() { return mState; } void oggPlayObject_impl::play() { arts_debug("oggvorbis: play"); mState = posPlaying; } void oggPlayObject_impl::halt() { mState = posIdle; union semun foo; if (child_pid) { arts_debug("oggvorbis: killing decoder process"); foo.val = 2*BACKBUFSIZ; semctl(buflen_sem, 1, SETVAL, foo); waitpid(child_pid, NULL, 0); child_pid = 0; } // tell the producer to exit (would also result in us halting, if we weren't already) // mainly this is to ensure that the decoder wakes up to notice } void oggPlayObject_impl::seek(const class poTime &t) { union semun foo; // this index is one-based so 0 can represent no seek foo.val = static_cast(t.seconds); arts_debug("requesting seek to %d", foo.val); semctl(buflen_sem, 2, SETVAL, foo); // we've done it } void oggPlayObject_impl::pause() { mState = posPaused; } void oggPlayObject_impl::streamInit() { arts_debug("oggvorbis: streamInit"); } void oggPlayObject_impl::streamStart() { arts_debug("ogg_vorbis: streamStart"); } void oggPlayObject_impl::calculateBlock(unsigned long samples) { int samplesAvailable = 0; //arts_debug("oggvorbis: calculateBlock"); if (mState == posPlaying) { //arts_debug("oggvorbis: calculateBlock, %d(%d) of %d samples in buffer", //shm_buf->buflen - bufpos, shm_buf->backbuflen,samples); struct sembuf bleh; bleh.sem_num = 0; bleh.sem_flg = IPC_NOWAIT; //arts_debug("oggvorbis: %d samples wanted", samplesAvailable); bleh.sem_op = -samples; // does the buffer have sufficient samples? if (semop(buflen_sem, &bleh, 1) == -1) { if (errno == EAGAIN) { union semun foo; arts_debug("oggvorbis: buffer underrun"); samplesAvailable = semctl(buflen_sem, 0, GETVAL, foo); // no samples AND no room is the decoder's way of signalling completion if (semctl(buflen_sem, 1, GETVAL, foo) == 0) { halt(); samplesAvailable = 0; } } else { // something awful has happened halt(); samplesAvailable = 0; } } else { samplesAvailable = samples; // number of samples we pushed from buffers // used to calculate the number we should zero out for an underrun } bleh.sem_flg = 0; // back to normal now //arts_debug("oggvorbis: %d samples available",samplesAvailable); for (int i = 0; i < samplesAvailable; ++i, buf_pos = ((buf_pos + 1) % BACKBUFSIZ)) { left[i] = shm_buf->left[buf_pos]; right[i] = shm_buf->right[buf_pos]; } bleh.sem_num = 1; bleh.sem_op = samplesAvailable; semop(buflen_sem, &bleh, 1); // mark the now-free space } // zero out any samples we didn't have enough to complete while(static_cast(samplesAvailable) < samples) { left[samplesAvailable] = 0.0; right[samplesAvailable] = 0.0; samplesAvailable++; } } void oggPlayObject_impl::streamEnd() { arts_debug("oggvorbis: streamEnd"); } REGISTER_IMPLEMENTATION(oggPlayObject_impl);