int tdav_producer_audio_set(tdav_producer_audio_t* self, const tmedia_param_t* param) { if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } if(param->plugin_type == tmedia_ppt_producer){ if(param->value_type == tmedia_pvt_int32){ if(tsk_striequals(param->key, "gain")){ uint32_t gain = TSK_TO_UINT32((uint8_t*)param->value); if(gain<TDAV_AUDIO_GAIN_MAX && gain>=0){ TMEDIA_PRODUCER(self)->audio.gain = (uint8_t)gain; TSK_DEBUG_INFO("audio producer gain=%u", gain); } else{ TSK_DEBUG_ERROR("%u is invalid as gain value", gain); return -2; } } else if(tsk_striequals(param->key, "volume")){ TMEDIA_PRODUCER(self)->audio.volume = TSK_TO_INT32((uint8_t*)param->value); TMEDIA_PRODUCER(self)->audio.volume = TSK_CLAMP(0, TMEDIA_PRODUCER(self)->audio.volume, 100); } } } return 0; }
int tdav_producer_send_data(tdav_producer_t140_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size) { if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } if(TMEDIA_PRODUCER(self)->enc_cb.callback){ if(data_type != tmedia_t140_data_type_utf8){ // build data tsk_size_t cmd_size = 0, i; int32_t cmd_val = (int32_t)data_type; if(data_ptr || data_size){ TSK_DEBUG_WARN("Data not expected for commands"); } // TODO: use ASM POPCNT for(i = 0; i < 32; i+= 8){ if(((cmd_val >> i) & 0xFF)){ ++cmd_size; } } if(cmd_size){ TMEDIA_PRODUCER(self)->enc_cb.callback(TMEDIA_PRODUCER(self)->enc_cb.callback_data, &cmd_val, cmd_size); } } else{
static void __handle_input_buffer (void *userdata, AudioQueueRef queue, AudioQueueBufferRef buffer, const AudioTimeStamp *start_time, UInt32 number_packet_descriptions, const AudioStreamPacketDescription *packet_descriptions ) { tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)userdata; if (!producer->started) { return; } // Alert the session that there is new data to send if(TMEDIA_PRODUCER(producer)->enc_cb.callback) { TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, buffer->mAudioData, buffer->mAudioDataByteSize); } // Re-enqueue the buffer AudioQueueEnqueueBuffer(producer->queue, buffer, 0, NULL); }
static OSStatus __handle_input_buffer(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { OSStatus status = noErr; tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)inRefCon; // holder AudioBuffer buffer; buffer.mData = tsk_null; buffer.mDataByteSize = 0; buffer.mNumberChannels = TMEDIA_PRODUCER(producer)->audio.channels; // list of holders AudioBufferList buffers; buffers.mNumberBuffers = 1; buffers.mBuffers[0] = buffer; // render to get frames from the system status = AudioUnitRender(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &buffers); if(status == 0){ tsk_mutex_lock(producer->ring.mutex); speex_buffer_write(producer->ring.buffer, buffers.mBuffers[0].mData, buffers.mBuffers[0].mDataByteSize); tsk_mutex_unlock(producer->ring.mutex); } return status; }
static void *__sender_thread(void *param) { TSK_DEBUG_INFO("__sender_thread::ENTER"); tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)param; uint32_t ptime = TMEDIA_PRODUCER(producer)->audio.ptime; tsk_ssize_t avail; // interval to sleep when using nonosleep() instead of conditional variable struct timespec interval; interval.tv_sec = (long)(ptime/1000); interval.tv_nsec = (long)(ptime%1000) * 1000000; // change thread priority //#if TARGET_OS_IPHONE __sender_thread_set_realtime(TMEDIA_PRODUCER(producer)->audio.ptime); //#endif // starts looping for (;;) { // wait for "ptime" milliseconds if(ptime <= kMaxPtimeBeforeUsingCondVars){ nanosleep(&interval, 0); } else { tsk_condwait_timedwait(producer->senderCondWait, (uint64_t)ptime); } // check state if(!producer->started){ break; } // read data and send them if(TMEDIA_PRODUCER(producer)->enc_cb.callback) { tsk_mutex_lock(producer->ring.mutex); avail = speex_buffer_get_available(producer->ring.buffer); while (producer->started && avail >= producer->ring.chunck.size) { avail -= speex_buffer_read(producer->ring.buffer, producer->ring.chunck.buffer, producer->ring.chunck.size); TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, producer->ring.chunck.buffer, producer->ring.chunck.size); } tsk_mutex_unlock(producer->ring.mutex); } else; } TSK_DEBUG_INFO("__sender_thread::EXIT"); return tsk_null; }
static int _tdav_producer_video_v4l2_grab(tdav_producer_video_v4l2_t* p_self) { int ret = 0, r; fd_set fds; struct timeval tv; if (!p_self) { V4L2_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_self); if (!p_self->b_started) { V4L2_DEBUG_ERROR("producer not started yet"); ret = -2; goto bail; } if (!TMEDIA_PRODUCER(p_self)->enc_cb.callback) { goto bail; } FD_ZERO(&fds); FD_SET(p_self->fd, &fds); /* Timeout. */ tv.tv_sec = 0; tv.tv_usec = (p_self->id_timer_grab * 1000); while (tv.tv_usec >= 1000000) { tv.tv_usec -= 1000000; tv.tv_sec++; } r = select(p_self->fd + 1, &fds, NULL, NULL, &tv); if (-1 == r) { if (EINTR == errno) { V4L2_DEBUG_INFO("select() returned EINTR"); } else { V4L2_DEBUG_ERROR("select() failed: %s error %d", strerror(errno), errno); } goto bail; } if (0 == r) { V4L2_DEBUG_INFO("select() timeout: %s error %d", strerror(errno), errno); goto bail; } // Grab a frame if ((ret = _v4l2_send_frame(p_self))) { goto bail; } bail: tsk_safeobj_unlock(p_self); return ret; }
/* constructor */ static tsk_object_t* _tdav_producer_video_v4l2_ctor(tsk_object_t *self, va_list * app) { tdav_producer_video_v4l2_t *p_v4l2 = (tdav_producer_video_v4l2_t *)self; if (p_v4l2) { /* init base */ tmedia_producer_init(TMEDIA_PRODUCER(p_v4l2)); TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_yuv420p; /* init self with default values*/ p_v4l2->fd = -1; TMEDIA_PRODUCER(p_v4l2)->video.fps = 15; TMEDIA_PRODUCER(p_v4l2)->video.width = 352; TMEDIA_PRODUCER(p_v4l2)->video.height = 288; tsk_safeobj_init(p_v4l2); } return self; }
/* ============ Media Producer Interface ================= */ int tdav_producer_waveapi_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec) { tdav_producer_waveapi_t* producer = (tdav_producer_waveapi_t*)self; tsk_size_t i; if(!producer || !codec && codec->plugin){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } TMEDIA_PRODUCER(producer)->audio.channels = codec->plugin->audio.channels; TMEDIA_PRODUCER(producer)->audio.rate = codec->plugin->rate; TMEDIA_PRODUCER(producer)->audio.ptime = codec->plugin->audio.ptime; /* codec should have ptime */ /* Format */ ZeroMemory(&producer->wfx, sizeof(WAVEFORMATEX)); producer->wfx.wFormatTag = WAVE_FORMAT_PCM; producer->wfx.nChannels = TMEDIA_PRODUCER(producer)->audio.channels; producer->wfx.nSamplesPerSec = TMEDIA_PRODUCER(producer)->audio.rate; producer->wfx.wBitsPerSample = TMEDIA_PRODUCER(producer)->audio.bits_per_sample; producer->wfx.nBlockAlign = (producer->wfx.nChannels * producer->wfx.wBitsPerSample/8); producer->wfx.nAvgBytesPerSec = (producer->wfx.nSamplesPerSec * producer->wfx.nBlockAlign); /* Average bytes (count) for each notification */ producer->bytes_per_notif = ((producer->wfx.nAvgBytesPerSec * TMEDIA_PRODUCER(producer)->audio.ptime)/1000); /* create buffers */ for(i = 0; i< sizeof(producer->hWaveHeaders)/sizeof(LPWAVEHDR); i++){ create_wavehdr(producer, i); } return 0; }
static int record_wavehdr(tdav_producer_waveapi_t* producer, LPWAVEHDR lpHdr) { MMRESULT result; if(!producer || !lpHdr || !producer->hWaveIn){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } // // Alert the session that there is new data to send over the network // if(TMEDIA_PRODUCER(producer)->callback){ TMEDIA_PRODUCER(producer)->callback(TMEDIA_PRODUCER(producer)->callback_data, lpHdr->lpData, (lpHdr->dwBytesRecorded/2)); } if(!producer->started){ return 0; } result = waveInUnprepareHeader(producer->hWaveIn, lpHdr, sizeof(WAVEHDR)); if(result != MMSYSERR_NOERROR){ print_last_error(result, "waveInUnprepareHeader"); return -2; } result = waveInPrepareHeader(producer->hWaveIn, lpHdr, sizeof(WAVEHDR)); if(result != MMSYSERR_NOERROR){ print_last_error(result, "waveInPrepareHeader"); return -3; } result = waveInAddBuffer(producer->hWaveIn, lpHdr, sizeof(WAVEHDR)); if(result != MMSYSERR_NOERROR){ print_last_error(result, "waveInAddBuffer"); return -4; } return 0; }
static OSStatus __handle_input_buffer(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { OSStatus status = noErr; tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)inRefCon; // holder AudioBuffer buffer; buffer.mData = tsk_null; buffer.mDataByteSize = 0; buffer.mNumberChannels = TMEDIA_PRODUCER(producer)->audio.channels; // list of holders AudioBufferList buffers; buffers.mNumberBuffers = 1; buffers.mBuffers[0] = buffer; // render to get frames from the system status = AudioUnitRender(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &buffers); if(status == 0){ // must not be done on async thread: doing it gives bad audio quality when audio+video call is done with CPU consuming codec (e.g. speex or g729) speex_buffer_write(producer->ring.buffer, buffers.mBuffers[0].mData, buffers.mBuffers[0].mDataByteSize); int avail = speex_buffer_get_available(producer->ring.buffer); while (producer->started && avail >= producer->ring.chunck.size) { avail -= speex_buffer_read(producer->ring.buffer, (void*)producer->ring.chunck.buffer, (int)producer->ring.chunck.size); TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, producer->ring.chunck.buffer, producer->ring.chunck.size); } } return status; }
/** Deinitialize a producer */ int tdav_producer_audio_deinit(tdav_producer_audio_t* self) { int ret; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* base */ if((ret = tmedia_producer_deinit(TMEDIA_PRODUCER(self)))){ return ret; } return ret; }
static void *__playback_thread(void *param) { tdav_producer_dsound_t* dsound = (tdav_producer_dsound_t*)param; HRESULT hr; LPVOID lpvAudio1, lpvAudio2; DWORD dwBytesAudio1, dwBytesAudio2; TSK_DEBUG_INFO("__record_thread -- START"); SetPriorityClass(GetCurrentThread(), REALTIME_PRIORITY_CLASS); for(;;){ DWORD dwEvent = WaitForMultipleObjects(sizeof(dsound->notifEvents)/sizeof(HANDLE), dsound->notifEvents, FALSE, INFINITE); if(!dsound->started){ break; } else { // lock if((hr = IDirectSoundCaptureBuffer_Lock(dsound->captureBuffer, (dwEvent * dsound->bytes_per_notif), dsound->bytes_per_notif, &lpvAudio1, &dwBytesAudio1, &lpvAudio2, &dwBytesAudio2, 0)) != DS_OK){ tdav_win32_print_error("IDirectSoundCaptureBuffer_Lock", hr); goto next; } if(TMEDIA_PRODUCER(dsound)->enc_cb.callback){ if(lpvAudio2){ TMEDIA_PRODUCER(dsound)->enc_cb.callback(TMEDIA_PRODUCER(dsound)->enc_cb.callback_data, lpvAudio1, dwBytesAudio1); TMEDIA_PRODUCER(dsound)->enc_cb.callback(TMEDIA_PRODUCER(dsound)->enc_cb.callback_data, lpvAudio2, dwBytesAudio2); } else{ TMEDIA_PRODUCER(dsound)->enc_cb.callback(TMEDIA_PRODUCER(dsound)->enc_cb.callback_data, lpvAudio1, dwBytesAudio1); } } // unlock if((hr = IDirectSoundCaptureBuffer_Unlock(dsound->captureBuffer, lpvAudio1, dwBytesAudio1, lpvAudio2, dwBytesAudio2)) != DS_OK){ tdav_win32_print_error("IDirectSoundCaptureBuffer_Unlock", hr); goto next; } next:; } } TSK_DEBUG_INFO("__record_thread -- STOP"); return tsk_null; }
/* destructor */ static tsk_object_t* _tdav_producer_video_v4l2_dtor(tsk_object_t * self) { tdav_producer_video_v4l2_t *p_v4l2 = (tdav_producer_video_v4l2_t *)self; if (p_v4l2) { /* stop */ if (p_v4l2->b_started) { _tdav_producer_video_v4l2_stop((tmedia_producer_t*)p_v4l2); } /* deinit base */ tmedia_producer_deinit(TMEDIA_PRODUCER(p_v4l2)); /* deinit self */ _v4l2_unprepare(p_v4l2); TSK_OBJECT_SAFE_FREE(p_v4l2->p_timer_mgr); tsk_safeobj_deinit(p_v4l2); V4L2_DEBUG_INFO("*** destroyed ***"); } return self; }
/** Initialize Audio producer * @param self The producer to initialize */ int tdav_producer_audio_init(tdav_producer_audio_t* self) { int ret; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* base */ if((ret = tmedia_producer_init(TMEDIA_PRODUCER(self)))){ return ret; } /* self (should be update by prepare() by using the codec's info)*/ self->bits_per_sample = TDAV_BITS_PER_SAMPLE_DEFAULT; self->channels = TDAV_CHANNELS_DEFAULT; self->rate = TDAV_RATE_DEFAULT; self->ptime = TDAV_PTIME_DEFAULT; return 0; }
/** Initialize Audio producer * @param self The producer to initialize */ int tdav_producer_audio_init(tdav_producer_audio_t* self) { int ret; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* base */ if((ret = tmedia_producer_init(TMEDIA_PRODUCER(self)))){ return ret; } /* self (should be update by prepare() by using the codec's info)*/ TMEDIA_PRODUCER(self)->audio.bits_per_sample = TDAV_BITS_PER_SAMPLE_DEFAULT; TMEDIA_PRODUCER(self)->audio.channels = TDAV_CHANNELS_DEFAULT; TMEDIA_PRODUCER(self)->audio.rate = TDAV_RATE_DEFAULT; TMEDIA_PRODUCER(self)->audio.ptime = TDAV_PTIME_DEFAULT; TMEDIA_PRODUCER(self)->audio.gain = TSK_MIN(tmedia_defaults_get_audio_producer_gain(), TDAV_AUDIO_GAIN_MAX); return 0; }
int32_t cmd_val = (int32_t)data_type; if(data_ptr || data_size){ TSK_DEBUG_WARN("Data not expected for commands"); } // TODO: use ASM POPCNT for(i = 0; i < 32; i+= 8){ if(((cmd_val >> i) & 0xFF)){ ++cmd_size; } } if(cmd_size){ TMEDIA_PRODUCER(self)->enc_cb.callback(TMEDIA_PRODUCER(self)->enc_cb.callback_data, &cmd_val, cmd_size); } } else{ TMEDIA_PRODUCER(self)->enc_cb.callback(TMEDIA_PRODUCER(self)->enc_cb.callback_data, data_ptr, data_size); } } return 0; } // // T.140 producer object definition // /* constructor */ static tsk_object_t* tdav_producer_t140_ctor(tsk_object_t * self, va_list * app) { tdav_producer_t140_t *producer = self; if(producer){ /* init base */
static int tdav_producer_audioqueue_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec) { OSStatus ret; tsk_size_t i; tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)self; if(!producer || !codec && codec->plugin){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } TMEDIA_PRODUCER(producer)->audio.channels = TMEDIA_CODEC_CHANNELS_AUDIO_ENCODING(codec); TMEDIA_PRODUCER(producer)->audio.rate = TMEDIA_CODEC_RATE_ENCODING(codec); TMEDIA_PRODUCER(producer)->audio.ptime = TMEDIA_CODEC_PTIME_AUDIO_ENCODING(codec); /* codec should have ptime */ // Set audio category #if TARGET_OS_IPHONE UInt32 category = kAudioSessionCategory_PlayAndRecord; AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category); #endif // Create the audio stream description AudioStreamBasicDescription *description = &(producer->description); description->mSampleRate = TMEDIA_PRODUCER(producer)->audio.rate; description->mFormatID = kAudioFormatLinearPCM; description->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; description->mChannelsPerFrame = TMEDIA_PRODUCER(producer)->audio.channels; description->mFramesPerPacket = 1; description->mBitsPerChannel = TMEDIA_PRODUCER(producer)->audio.bits_per_sample; description->mBytesPerPacket = description->mBitsPerChannel / 8 * description->mChannelsPerFrame; description->mBytesPerFrame = description->mBytesPerPacket; description->mReserved = 0; int packetperbuffer = 1000 / TMEDIA_PRODUCER(producer)->audio.ptime; producer->buffer_size = description->mSampleRate * description->mBytesPerFrame / packetperbuffer; // Create the record audio queue ret = AudioQueueNewInput(&(producer->description), __handle_input_buffer, producer, NULL, kCFRunLoopCommonModes, 0, &(producer->queue)); for(i = 0; i < CoreAudioRecordBuffers; i++) { // Create the buffer for the queue ret = AudioQueueAllocateBuffer(producer->queue, producer->buffer_size, &(producer->buffers[i])); if (ret) { break; } // Clear the data memset(producer->buffers[i]->mAudioData, 0, producer->buffer_size); producer->buffers[i]->mAudioDataByteSize = producer->buffer_size; // Enqueue the buffer ret = AudioQueueEnqueueBuffer(producer->queue, producer->buffers[i], 0, NULL); if (ret) { break; } } return 0; }
static int _v4l2_send_frame(tdav_producer_video_v4l2_t* p_self) { struct v4l2_buffer buf; unsigned int i; #define V4L2_SEND_BUFF(_buff, _size) \ TMEDIA_PRODUCER(p_self)->enc_cb.callback(TMEDIA_PRODUCER(p_self)->enc_cb.callback_data, (_buff), (_size)); #if V4L2_FAKE_UYVY { tsk_size_t size = (TMEDIA_PRODUCER(p_self)->video.width * TMEDIA_PRODUCER(p_self)->video.height) << 1; uint8_t* buff = (uint8_t*)tsk_malloc(size); if (buff) { tsk_size_t i; for (i = 0; i < size; ++i) { buff[i] = rand() & 254; } V4L2_SEND_BUFF(buff, size); tsk_free((void**)&buff); } return 0; } #endif switch (p_self->io) { case V4L2_IO_METHOD_READ: if (-1 == read(p_self->fd, p_self->p_buffers[0].p_start, p_self->p_buffers[0].n_length)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: V4L2_DEBUG_ERROR("read() failed: %s error %d", strerror(errno), errno); break; } } V4L2_SEND_BUFF(p_self->p_buffers[0].p_start, p_self->p_buffers[0].n_length); return 0; case V4L2_IO_METHOD_MMAP: V4L2_CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: V4L2_DEBUG_INFO("EAGAIN"); return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: V4L2_DEBUG_ERROR("xioctl(VIDIOC_DQBUF) failed: %s error %d", strerror(errno), errno); break; } } assert(buf.index < p_self->n_buffers); V4L2_SEND_BUFF(p_self->p_buffers[buf.index].p_start, buf.bytesused); if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_QBUF, &buf)) { V4L2_DEBUG_ERROR("xioctl(VIDIOC_DQBUF) failed: %s error %d", strerror(errno), errno); break; } return 0; case V4L2_IO_METHOD_USERPTR: V4L2_CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: V4L2_DEBUG_INFO("EAGAIN"); return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: V4L2_DEBUG_ERROR("xioctl(VIDIOC_DQBUF) failed: %s error %d", strerror(errno), errno); break; } } for (i = 0; i < p_self->n_buffers; ++i) { if (buf.m.userptr == (unsigned long)p_self->p_buffers[i].p_start && buf.length == p_self->p_buffers[i].n_length) { break; } } V4L2_SEND_BUFF((void *)buf.m.userptr, buf.bytesused); if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_QBUF, &buf)) { V4L2_DEBUG_ERROR("xioctl(VIDIOC_DQBUF) failed: %s error %d", strerror(errno), errno); break; } return 0; } return -1; }
static int _v4l2_get_best_format(tdav_producer_video_v4l2_t* p_self, const char* device_name, struct v4l2_format* fmt_ret) { struct v4l2_format fmt, fmt_default, fmt_best; struct v4l2_fmtdesc fmtdesc; int i, j, field, size; int ok = 0; if (!fmt_ret) { V4L2_DEBUG_ERROR("Invalid parameter"); return -1; } // get default format V4L2_CLEAR(fmt_default); fmt_default.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (_v4l2_xioctl(p_self->fd, VIDIOC_G_FMT, &fmt_default) == -1) { V4L2_DEBUG_ERROR("xioctl(%s, VIDIOC_G_FMT) failed: %s error %d", device_name, strerror(errno), errno); return -1; } V4L2_DEBUG_INFO("device '%s' default format: width:%d, height:%d, field:%d, pixelformat:%d", device_name, fmt_default.fmt.pix.width, fmt_default.fmt.pix.height, fmt_default.fmt.pix.field, fmt_default.fmt.pix.pixelformat); /* Best format (using preference) */ V4L2_CLEAR(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; for (i = 0; i < sizeof(pix_format_prefs)/sizeof(pix_format_prefs[0]); ++i) { for (size = 0; size < 2; ++size) { for (field = 0; field < 2; ++field) { fmt.fmt.pix.width = (size == 0) ? TMEDIA_PRODUCER(p_self)->video.width : fmt_default.fmt.pix.width; fmt.fmt.pix.height = (size == 0) ? TMEDIA_PRODUCER(p_self)->video.height : fmt_default.fmt.pix.height; fmt.fmt.pix.pixelformat = pix_format_prefs[i]; fmt.fmt.pix.field = (field == 0) ? V4L2_FIELD_NONE : V4L2_FIELD_INTERLACED; if ((ok = (_v4l2_xioctl(p_self->fd, VIDIOC_TRY_FMT, &fmt) != -1))) { goto bail; } } } } /* Best format (using caps) */ for (i = 0; ; ++i) { V4L2_CLEAR(fmtdesc); fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmtdesc.index = i; if (_v4l2_xioctl(p_self->fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) { break; } V4L2_DEBUG_INFO("CAPS: device name=%s, fmtdesc index=%d, type=%d, description=%s, pixelformat=%d", device_name, fmtdesc.index, fmtdesc.type, fmtdesc.description, fmtdesc.pixelformat); for (j = 0; j < sizeof(pix_format_prefs)/sizeof(pix_format_prefs[0]); ++j) { if (fmtdesc.pixelformat == pix_format_prefs[j]) { for (size = 0; size < 2; ++size) { for (field = 0; field < 2; ++field) { fmt.fmt.pix.width = (size == 0) ? TMEDIA_PRODUCER(p_self)->video.width : fmt_default.fmt.pix.width; fmt.fmt.pix.height = (size == 0) ? TMEDIA_PRODUCER(p_self)->video.height : fmt_default.fmt.pix.height; fmt.fmt.pix.pixelformat = pix_format_prefs[i]; fmt.fmt.pix.field = (field == 0) ? V4L2_FIELD_NONE : V4L2_FIELD_INTERLACED; if ((ok = (_v4l2_xioctl(p_self->fd, VIDIOC_TRY_FMT, &fmt) != -1))) { goto bail; } } } } } } bail: if (ok) { memcpy(fmt_ret, &fmt, sizeof(fmt)); } return ok ? 0 : -1; }
static int _v4l2_prepare(tdav_producer_video_v4l2_t* p_self) { const char* device_names[] = { tmedia_producer_get_friendly_name(TMEDIA_PRODUCER(p_self)->plugin->type), "/dev/video0", }; // FIXME: VIDIOC_C_ENUM_INPUT and choose best one const char* device_name; int i, err = -1; struct stat st; unsigned int min; V4L2_DEBUG_INFO("--- PREPARE ---"); if (p_self->fd > 0) { V4L2_DEBUG_WARN("Producer already prepared"); return 0; } for (i = 0; i < sizeof(device_names)/sizeof(device_names[0]); ++i) { if ((device_name = device_names[i])) { V4L2_DEBUG_INFO("Preparing '%s'...", device_name); if (stat(device_name, &st) == -1) { V4L2_DEBUG_WARN("stat('%s'): %d, %s", device_name, errno, strerror(errno)); continue; } if (!S_ISCHR(st.st_mode)) { V4L2_DEBUG_WARN("'%s' not a valid device", device_name); continue; } if ((p_self->fd = open(device_name, O_RDWR /* required */ | O_NONBLOCK, 0)) == -1) { V4L2_DEBUG_WARN("Failed to open '%s': %d, %s\n", device_name, errno, strerror(errno)); continue; } V4L2_DEBUG_INFO("'%s' successfully opened", device_name); } } if (p_self->fd == -1) { V4L2_DEBUG_ERROR("No valid device found"); goto bail; } if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_QUERYCAP, &p_self->cap)) { if (EINVAL == errno) { V4L2_DEBUG_ERROR("%s is no V4L2 device", device_name); goto bail; } else { V4L2_DEBUG_ERROR("xioctl(%s, VIDIOC_QUERYCAP) failed: %s error %d", device_name, strerror(errno), errno); goto bail; } } if (!(p_self->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { V4L2_DEBUG_ERROR("%s is no video capture device", device_name); goto bail; } // Get best io method p_self->io = V4L2_IO_METHOD_NONE; for (i = 0; i < sizeof(io_method_prefs)/sizeof(io_method_prefs[0]) && p_self->io == V4L2_IO_METHOD_NONE; ++i) { V4L2_DEBUG_INFO("Trying with io method=%d", io_method_prefs[i]); switch (io_method_prefs[i]) { case V4L2_IO_METHOD_READ: if (!(p_self->cap.capabilities & V4L2_CAP_READWRITE)) { V4L2_DEBUG_WARN("%s does not support read i/o", device_name); continue; } p_self->io = io_method_prefs[i]; break; case V4L2_IO_METHOD_MMAP: case V4L2_IO_METHOD_USERPTR: if (!(p_self->cap.capabilities & V4L2_CAP_STREAMING)) { V4L2_DEBUG_WARN("%s does not support streaming i/o", device_name); continue; } p_self->io = io_method_prefs[i]; break; } } if (p_self->io == V4L2_IO_METHOD_NONE) { V4L2_DEBUG_ERROR("Failed to peek an i/o method for '%s' device", device_name); goto bail; } V4L2_DEBUG_INFO("i/o method for '%s' device is %d", device_name, p_self->io); /* Select video input, video standard and tune here. */ V4L2_CLEAR(p_self->cropcap); p_self->cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (0 == _v4l2_xioctl(p_self->fd, VIDIOC_CROPCAP, &p_self->cropcap)) { p_self->crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; p_self->crop.c = p_self->cropcap.defrect; /* reset to default */ if (-1 == _v4l2_xioctl(p_self->fd, VIDIOC_S_CROP, &p_self->crop)) { switch (errno) { case EINVAL: default: V4L2_DEBUG_INFO("'%s' device doesn't support cropping", device_name); break; } } else { V4L2_DEBUG_INFO("'%s' device supports cropping with type = %d", device_name, p_self->crop.type); } } else { V4L2_DEBUG_INFO("'%s' device doesn't support cropping", device_name); } /* Best format */ V4L2_CLEAR(p_self->fmt); // get() if (_v4l2_get_best_format(p_self, device_name, &p_self->fmt) != 0) { V4L2_DEBUG_ERROR("Failed to peek best format for '%s' device", device_name); goto bail; } // set() if (_v4l2_xioctl(p_self->fd, VIDIOC_S_FMT, &p_self->fmt) == -1) { goto bail; } V4L2_DEBUG_INFO("device '%s' best format: width:%d, height:%d, field:%d, pixelformat:%d", device_name, p_self->fmt.fmt.pix.width, p_self->fmt.fmt.pix.height, p_self->fmt.fmt.pix.field, p_self->fmt.fmt.pix.pixelformat); /* Buggy driver paranoia. */ #if 1 min = p_self->fmt.fmt.pix.width * 2; if (p_self->fmt.fmt.pix.bytesperline < min) { p_self->fmt.fmt.pix.bytesperline = min; } min = p_self->fmt.fmt.pix.bytesperline * p_self->fmt.fmt.pix.height; if (p_self->fmt.fmt.pix.sizeimage < min) { p_self->fmt.fmt.pix.sizeimage = min; } #endif switch (p_self->io) { case V4L2_IO_METHOD_READ: if (_v4l2_init_read(p_self, p_self->fmt.fmt.pix.sizeimage) != 0) { goto bail; } break; case V4L2_IO_METHOD_MMAP: if (_v4l2_init_mmap(p_self, device_name) != 0) { goto bail; } break; case V4L2_IO_METHOD_USERPTR: if (_v4l2_init_userp(p_self, p_self->fmt.fmt.pix.sizeimage, device_name) != 0) { goto bail; } break; } V4L2_DEBUG_INFO("'%s' device initialized using i/o method=%d", device_name, p_self->io); // all is OK err = 0; bail: if (err) { _v4l2_unprepare(p_self); } else { V4L2_DEBUG_INFO("Prepared :)"); } return err; }
static int _tdav_producer_video_v4l2_prepare(tmedia_producer_t* p_self, const tmedia_codec_t* pc_codec) { tdav_producer_video_v4l2_t* p_v4l2 = (tdav_producer_video_v4l2_t*)p_self; int ret = 0; if (!p_v4l2 || !pc_codec) { V4L2_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_v4l2); if (!p_v4l2->p_timer_mgr && !(p_v4l2->p_timer_mgr = tsk_timer_manager_create())) { V4L2_DEBUG_ERROR("Failed to create timer manager"); ret = -2; goto bail; } TMEDIA_PRODUCER(p_v4l2)->video.fps = TMEDIA_CODEC_VIDEO(pc_codec)->out.fps; TMEDIA_PRODUCER(p_v4l2)->video.width = TMEDIA_CODEC_VIDEO(pc_codec)->out.width; TMEDIA_PRODUCER(p_v4l2)->video.height = TMEDIA_CODEC_VIDEO(pc_codec)->out.height; p_v4l2->u_timout_grab = (1000/TMEDIA_PRODUCER(p_v4l2)->video.fps); // prepare() if ((ret = _v4l2_prepare(p_v4l2))) { goto bail; } // update() - up to the "converter" to perform chroma conversion and scaling TMEDIA_PRODUCER(p_v4l2)->video.width = p_v4l2->fmt.fmt.pix.width; TMEDIA_PRODUCER(p_v4l2)->video.height = p_v4l2->fmt.fmt.pix.height; #if V4L2_FAKE_UYVY TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_uyvy422; #else switch (p_v4l2->fmt.fmt.pix.pixelformat) { case V4L2_PIX_FMT_YUV420: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_yuv420p; break; case V4L2_PIX_FMT_NV12: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_nv12; break; case V4L2_PIX_FMT_NV21: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_nv21; break; case V4L2_PIX_FMT_YUYV: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_yuyv422; break; case V4L2_PIX_FMT_UYVY: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_uyvy422; // SINCITY break; case V4L2_PIX_FMT_RGB24: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_rgb24; break; case V4L2_PIX_FMT_RGB32: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_rgb32; break; case V4L2_PIX_FMT_MJPEG: TMEDIA_PRODUCER(p_v4l2)->video.chroma = tmedia_chroma_mjpeg; break; default: V4L2_DEBUG_ERROR("Failed to match negotiated format: %d", p_v4l2->fmt.fmt.pix.pixelformat); ret = -1; goto bail; } #endif /* V4L2_FAKE_UYVY */ V4L2_DEBUG_INFO("Negotiated caps: fps=%d, width=%d, height=%d, chroma=%d", TMEDIA_PRODUCER(p_v4l2)->video.fps, TMEDIA_PRODUCER(p_v4l2)->video.width, TMEDIA_PRODUCER(p_v4l2)->video.height, TMEDIA_PRODUCER(p_v4l2)->video.chroma); p_v4l2->b_prepared = (ret == 0) ? tsk_true : tsk_false; bail: tsk_safeobj_unlock(p_v4l2); return ret; }
static int tdav_producer_audiounit_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec) { static UInt32 flagOne = 1; UInt32 param; // static UInt32 flagZero = 0; #define kInputBus 1 tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; OSStatus status = noErr; AudioStreamBasicDescription audioFormat; AudioStreamBasicDescription deviceFormat; if(!producer || !codec || !codec->plugin){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } if(!producer->audioUnitHandle){ if(!(producer->audioUnitHandle = tdav_audiounit_handle_create(TMEDIA_PRODUCER(producer)->session_id))){ TSK_DEBUG_ERROR("Failed to get audio unit instance for session with id=%lld", TMEDIA_PRODUCER(producer)->session_id); return -3; } } // enable status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flagOne, sizeof(flagOne)); if(status != noErr){ TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%ld", (signed long)status); return -4; } else { #if !TARGET_OS_IPHONE // strange: TARGET_OS_MAC is equal to '1' on Smulator // disable output param = 0; status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, ¶m, sizeof(UInt32)); if(status != noErr){ TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%ld", (signed long)status); return -4; } // set default audio device param = sizeof(AudioDeviceID); AudioDeviceID inputDeviceID; status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, ¶m, &inputDeviceID); if(status != noErr){ TSK_DEBUG_ERROR("AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice) failed with status=%ld", (signed long)status); return -4; } // set the current device to the default input unit status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Output, 0, &inputDeviceID, sizeof(AudioDeviceID)); if(status != noErr){ TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_CurrentDevice) failed with status=%ld", (signed long)status); return -4; } #endif /* TARGET_OS_MAC */ /* codec should have ptime */ TMEDIA_PRODUCER(producer)->audio.channels = codec->plugin->audio.channels; TMEDIA_PRODUCER(producer)->audio.rate = codec->plugin->rate; TMEDIA_PRODUCER(producer)->audio.ptime = codec->plugin->audio.ptime; // get device format param = sizeof(AudioStreamBasicDescription); status = AudioUnitGetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kInputBus, &deviceFormat, ¶m); if(status == noErr && deviceFormat.mSampleRate){ #if TARGET_OS_IPHONE // iOS support 8Khz, 16kHz and 32kHz => do not override the sampleRate #elif TARGET_OS_MAC // For example, iSight supports only 48kHz TMEDIA_PRODUCER(producer)->audio.rate = deviceFormat.mSampleRate; #endif } // set format audioFormat.mSampleRate = TMEDIA_PRODUCER(producer)->audio.rate; audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; audioFormat.mChannelsPerFrame = TMEDIA_PRODUCER(producer)->audio.channels; audioFormat.mFramesPerPacket = 1; audioFormat.mBitsPerChannel = TMEDIA_PRODUCER(producer)->audio.bits_per_sample; audioFormat.mBytesPerPacket = audioFormat.mBitsPerChannel / 8 * audioFormat.mChannelsPerFrame; audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket; audioFormat.mReserved = 0; if(audioFormat.mFormatID == kAudioFormatLinearPCM && audioFormat.mChannelsPerFrame == 1){ audioFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; } status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat)); if(status){ TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed with status=%ld", (signed long)status); return -5; } else { // configure if(tdav_audiounit_handle_configure(producer->audioUnitHandle, tsk_false, TMEDIA_PRODUCER(producer)->audio.ptime, &audioFormat)){ TSK_DEBUG_ERROR("tdav_audiounit_handle_set_rate(%d) failed", TMEDIA_PRODUCER(producer)->audio.rate); return -4; } // set callback function AURenderCallbackStruct callback; callback.inputProc = __handle_input_buffer; callback.inputProcRefCon = producer; status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, kInputBus, &callback, sizeof(callback)); if(status){ TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_SetInputCallback) failed with status=%ld", (signed long)status); return -6; } else { // disbale buffer allocation as we will provide ours //status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), // kAudioUnitProperty_ShouldAllocateBuffer, // kAudioUnitScope_Output, // kInputBus, // &flagZero, // sizeof(flagZero)); int packetperbuffer = (1000 / codec->plugin->audio.ptime); producer->ring.chunck.size = audioFormat.mSampleRate * audioFormat.mBytesPerFrame / packetperbuffer; // allocate our chunck buffer if(!(producer->ring.chunck.buffer = tsk_realloc(producer->ring.chunck.buffer, producer->ring.chunck.size))){ TSK_DEBUG_ERROR("Failed to allocate new buffer"); return -7; } // create mutex for ring buffer if(!producer->ring.mutex && !(producer->ring.mutex = tsk_mutex_create_2(tsk_false))){ TSK_DEBUG_ERROR("Failed to create new mutex"); return -8; } // create ringbuffer producer->ring.size = kRingPacketCount * producer->ring.chunck.size; if(!producer->ring.buffer){ producer->ring.buffer = speex_buffer_init(producer->ring.size); } else { int ret; if((ret = speex_buffer_resize(producer->ring.buffer, producer->ring.size)) < 0){ TSK_DEBUG_ERROR("speex_buffer_resize(%d) failed with error code=%d", producer->ring.size, ret); return ret; } } if(!producer->ring.buffer){ TSK_DEBUG_ERROR("Failed to create a new ring buffer with size = %d", producer->ring.size); return -9; } } } } TSK_DEBUG_INFO("AudioUnit producer prepared"); return tdav_audiounit_handle_signal_producer_prepared(producer->audioUnitHandle); }