/*---------------------------------------------------------------------- | AlsaOutput_PutPacket +---------------------------------------------------------------------*/ BLT_METHOD AlsaOutput_PutPacket(BLT_PacketConsumer* _self, BLT_MediaPacket* packet) { AlsaOutput* self = ATX_SELF(AlsaOutput, BLT_PacketConsumer); const BLT_PcmMediaType* media_type; BLT_ByteBuffer buffer; BLT_Size size; BLT_Result result; /* check parameters */ if (packet == NULL) { return BLT_ERROR_INVALID_PARAMETERS; } /* check the payload buffer and size */ buffer = BLT_MediaPacket_GetPayloadBuffer(packet); size = BLT_MediaPacket_GetPayloadSize(packet); if (size == 0) return BLT_SUCCESS; /* get the media type */ result = BLT_MediaPacket_GetMediaType(packet, (const BLT_MediaType**)(const void*)&media_type); if (BLT_FAILED(result)) return result; /* check the media type */ if (media_type->base.id != BLT_MEDIA_TYPE_ID_AUDIO_PCM) { return BLT_ERROR_INVALID_MEDIA_TYPE; } /* configure the device for this format */ result = AlsaOutput_Configure(self, media_type); if (BLT_FAILED(result)) return result; /* update the media time */ { BLT_TimeStamp ts = BLT_MediaPacket_GetTimeStamp(packet); ATX_UInt64 ts_nanos = BLT_TimeStamp_ToNanos(ts); BLT_TimeStamp packet_duration; if (media_type->sample_rate && media_type->channel_count && media_type->bits_per_sample) { unsigned int sample_count = BLT_MediaPacket_GetPayloadSize(packet)/ (media_type->channel_count*media_type->bits_per_sample/8); packet_duration = BLT_TimeStamp_FromSamples(sample_count, media_type->sample_rate); } else { packet_duration = BLT_TimeStamp_FromSeconds(0); } if (ts_nanos == 0) { self->media_time = self->next_media_time; } else { self->media_time = ts_nanos; } self->next_media_time = self->media_time+BLT_TimeStamp_ToNanos(packet_duration); } /* write the audio samples */ return AlsaOutput_Write(self, buffer, size); }
/*---------------------------------------------------------------------- | AlsaOutput_Seek +---------------------------------------------------------------------*/ BLT_METHOD AlsaOutput_Seek(BLT_MediaNode* _self, BLT_SeekMode* mode, BLT_SeekPoint* point) { AlsaOutput* self = ATX_SELF_EX(AlsaOutput, BLT_BaseMediaNode, BLT_MediaNode); BLT_COMPILER_UNUSED(mode); BLT_COMPILER_UNUSED(point); /* ignore unless we're prepared */ if (self->state != BLT_ALSA_OUTPUT_STATE_PREPARED) { return BLT_SUCCESS; } /* reset the device */ AlsaOutput_Reset(self); /* update the media time */ if (point->mask & BLT_SEEK_POINT_MASK_TIME_STAMP) { self->media_time = BLT_TimeStamp_ToNanos(point->time_stamp); self->next_media_time = self->media_time; } else { self->media_time = 0; self->next_media_time = 0; } return BLT_SUCCESS; }
/*---------------------------------------------------------------------- | OsxAudioUnitsOutput_GetStatus +---------------------------------------------------------------------*/ BLT_METHOD OsxAudioUnitsOutput_GetStatus(BLT_OutputNode* _self, BLT_OutputNodeStatus* status) { OsxAudioUnitsOutput* self = ATX_SELF(OsxAudioUnitsOutput, BLT_OutputNode); /* default values */ status->flags = 0; pthread_mutex_lock(&self->lock); /* check if the queue is full */ if (ATX_List_GetItemCount(self->packet_queue) >= self->max_packets_in_queue) { ATX_LOG_FINER("packet queue is full"); status->flags |= BLT_OUTPUT_NODE_STATUS_QUEUE_FULL; } /* compute the media time */ BLT_TimeStamp_Set(status->media_time, 0, 0); if (self->media_time_snapshot.rendered_host_time) { UInt64 host_time = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); UInt64 media_time = BLT_TimeStamp_ToNanos(self->media_time_snapshot.rendered_packet_ts); ATX_LOG_FINER_3("host time = %lld, last rendered packet = %lld, rendered ts = %lld", host_time, self->media_time_snapshot.rendered_host_time, media_time); if (host_time > self->media_time_snapshot.rendered_host_time) { media_time += host_time-self->media_time_snapshot.rendered_host_time; } UInt64 max_media_time; max_media_time = BLT_TimeStamp_ToNanos(self->media_time_snapshot.rendered_packet_ts) + BLT_TimeStamp_ToNanos(self->media_time_snapshot.rendered_duration); ATX_LOG_FINER_2("computed media time = %lld, max media time = %lld", media_time, max_media_time); if (media_time > max_media_time) { ATX_LOG_FINER("media time clamped to max"); media_time = max_media_time; } status->media_time = BLT_TimeStamp_FromNanos(media_time); ATX_LOG_FINER_1("media time = %lld", media_time); } pthread_mutex_unlock(&self->lock); return BLT_SUCCESS; }
/*---------------------------------------------------------------------- | BLT_DecoderServer::UpdateStatus +---------------------------------------------------------------------*/ BLT_Result BLT_DecoderServer::UpdateStatus() { BLT_DecoderStatus status; BLT_Result result; // get the decoder status result = BLT_Decoder_GetStatus(m_Decoder, &status); if (BLT_FAILED(result)) return result; // notify if the time has changed by more than the update threshold // NOTE: current and previous here are measured in milliseconds ATX_UInt64 previous = BLT_TimeStamp_ToNanos(m_DecoderStatus.time_stamp)/1000000; ATX_UInt64 current = BLT_TimeStamp_ToNanos(status.time_stamp)/1000000; if (m_TimeStampUpdateQuantum) { // make the new time stamp a multiple of the update quantum current /= m_TimeStampUpdateQuantum; current *= m_TimeStampUpdateQuantum; } if (current != previous) { m_DecoderStatus.time_stamp = BLT_TimeStamp_FromMillis(current); NotifyTimeCode(); } // convert the stream position into a decoder position if (m_PositionUpdateRange != 0) { ATX_UInt64 ratio = status.position.range/m_PositionUpdateRange; ATX_UInt64 offset; if (ratio == 0) { offset = 0; } else { offset = status.position.offset/ratio; } if (offset != m_DecoderStatus.position.offset) { m_DecoderStatus.position.offset = offset; NotifyPosition(); } } return BLT_SUCCESS; }
/*---------------------------------------------------------------------- | AlsaOutput_GetStatus +---------------------------------------------------------------------*/ BLT_METHOD AlsaOutput_GetStatus(BLT_OutputNode* _self, BLT_OutputNodeStatus* status) { AlsaOutput* self = ATX_SELF(AlsaOutput, BLT_OutputNode); snd_pcm_status_t* pcm_status; snd_pcm_sframes_t delay = 0; int io_result; /* default values */ status->media_time.seconds = 0; status->media_time.nanoseconds = 0; status->flags = 0; /* get the driver status */ snd_pcm_status_alloca_no_assert(&pcm_status); io_result = snd_pcm_status(self->device_handle, pcm_status); if (io_result != 0) { return BLT_FAILURE; } delay = snd_pcm_status_get_delay(pcm_status); if (delay == 0) { /* workaround buggy alsa drivers */ io_result = snd_pcm_delay(self->device_handle, &delay); if (io_result != 0) { return BLT_FAILURE; } } if (delay > 0 && self->media_type.sample_rate) { ATX_UInt64 media_time_samples = (self->next_media_time * (ATX_UInt64)self->media_type.sample_rate)/ (ATX_UInt64)1000000000; ATX_UInt64 media_time_ns; if (delay <= (snd_pcm_sframes_t)media_time_samples) { media_time_samples -= delay; } else { media_time_samples = 0; } media_time_ns = (media_time_samples*(ATX_UInt64)1000000000)/self->media_type.sample_rate; status->media_time = BLT_TimeStamp_FromNanos(media_time_ns); } else { status->media_time = BLT_TimeStamp_FromNanos(self->next_media_time); } /* return the computed media time */ ATX_LOG_FINEST_3("delay = %lld samples, input port time = %lld, media time = %lld", (ATX_UInt64)delay, self->next_media_time, BLT_TimeStamp_ToNanos(status->media_time)); return BLT_SUCCESS; }
/*---------------------------------------------------------------------- | OsxAudioUnitsOutput_RenderCallback +---------------------------------------------------------------------*/ static OSStatus OsxAudioUnitsOutput_RenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { OsxAudioUnitsOutput* self = (OsxAudioUnitsOutput*)inRefCon; ATX_ListItem* item; unsigned int requested; unsigned char* out; ATX_Boolean timestamp_measured = ATX_FALSE; BLT_COMPILER_UNUSED(ioActionFlags); BLT_COMPILER_UNUSED(inTimeStamp); BLT_COMPILER_UNUSED(inBusNumber); BLT_COMPILER_UNUSED(inNumberFrames); /* sanity check on the parameters */ if (ioData == NULL || ioData->mNumberBuffers == 0) return 0; /* in case we have a strange request with more than one buffer, just return silence */ if (ioData->mNumberBuffers != 1) { unsigned int i; ATX_LOG_FINEST_1("strange request with %d buffers", (int)ioData->mNumberBuffers); for (i=0; i<ioData->mNumberBuffers; i++) { ATX_SetMemory(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize); } return 0; } /* init local variables */ requested = ioData->mBuffers[0].mDataByteSize; out = (unsigned char*)ioData->mBuffers[0].mData; ATX_LOG_FINEST_2("request for %d bytes, %d frames", (int)requested, (int)inNumberFrames); /* lock the packet queue */ pthread_mutex_lock(&self->lock); /* return now if we're paused */ //if (self->paused) goto end; /* abort early if we have no packets */ if (ATX_List_GetItemCount(self->packet_queue) == 0) goto end; /* fill as much as we can */ while (requested && (item = ATX_List_GetFirstItem(self->packet_queue))) { BLT_MediaPacket* packet = ATX_ListItem_GetData(item); const BLT_PcmMediaType* media_type; BLT_Size payload_size; BLT_Size chunk_size; BLT_TimeStamp chunk_duration; BLT_TimeStamp packet_ts; unsigned int bytes_per_frame; unsigned int sample_rate; /* get the packet info */ BLT_MediaPacket_GetMediaType(packet, (const BLT_MediaType**)&media_type); packet_ts = BLT_MediaPacket_GetTimeStamp(packet); bytes_per_frame = media_type->channel_count*media_type->bits_per_sample/8; sample_rate = media_type->sample_rate; /* record the timestamp if we have not already done so */ if (!timestamp_measured) { self->media_time_snapshot.rendered_packet_ts = packet_ts; self->media_time_snapshot.rendered_host_time = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); BLT_TimeStamp_Set(self->media_time_snapshot.rendered_duration, 0, 0); timestamp_measured = ATX_TRUE; ATX_LOG_FINEST_2("rendered TS: packet ts=%lld, host ts=%lld", BLT_TimeStamp_ToNanos(packet_ts), self->media_time_snapshot.rendered_host_time); } /* compute how much to copy from this packet */ payload_size = BLT_MediaPacket_GetPayloadSize(packet); if (payload_size <= requested) { /* copy the entire payload and remove the packet from the queue */ chunk_size = payload_size; ATX_CopyMemory(out, BLT_MediaPacket_GetPayloadBuffer(packet), chunk_size); ATX_List_RemoveItem(self->packet_queue, item); packet = NULL; media_type = NULL; ATX_LOG_FINER_1("media packet fully consumed, %d left in queue", ATX_List_GetItemCount(self->packet_queue)); } else { /* only copy a portion of the payload */ chunk_size = requested; ATX_CopyMemory(out, BLT_MediaPacket_GetPayloadBuffer(packet), chunk_size); } /* update the counters */ requested -= chunk_size; out += chunk_size; /* update the media time snapshot */ if (bytes_per_frame) { unsigned int frames_in_chunk = chunk_size/bytes_per_frame; chunk_duration = BLT_TimeStamp_FromSamples(frames_in_chunk, sample_rate); } else { BLT_TimeStamp_Set(chunk_duration, 0, 0); } self->media_time_snapshot.rendered_duration = BLT_TimeStamp_Add(self->media_time_snapshot.rendered_duration, chunk_duration); /* update the packet unless we're done with it */ if (packet) { /* update the packet offset and timestamp */ BLT_MediaPacket_SetPayloadOffset(packet, BLT_MediaPacket_GetPayloadOffset(packet)+chunk_size); BLT_MediaPacket_SetTimeStamp(packet, BLT_TimeStamp_Add(packet_ts, chunk_duration)); } } end: /* fill whatever is left with silence */ if (requested) { ATX_LOG_FINEST_1("filling with %d bytes of silence", requested); ATX_SetMemory(out, 0, requested); } pthread_mutex_unlock(&self->lock); return 0; }