void decoder_flush_chunk(struct decoder *decoder) { struct decoder_control *dc = decoder->dc; assert(decoder != NULL); assert(decoder->chunk != NULL); if (music_chunk_is_empty(decoder->chunk)) music_buffer_return(dc->buffer, decoder->chunk); else music_pipe_push(dc->pipe, decoder->chunk); decoder->chunk = NULL; }
static const char * ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, struct filter *replay_gain_filter, unsigned *replay_gain_serial_p, size_t *length_r) { assert(chunk != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); const char *data = chunk->data; size_t length = chunk->length; (void)ao; assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); if (length > 0 && replay_gain_filter != NULL) { if (chunk->replay_gain_serial != *replay_gain_serial_p) { replay_gain_filter_set_info(replay_gain_filter, chunk->replay_gain_serial != 0 ? &chunk->replay_gain_info : NULL); *replay_gain_serial_p = chunk->replay_gain_serial; } GError *error = NULL; data = filter_filter(replay_gain_filter, data, length, &length, &error); if (data == NULL) { g_warning("\"%s\" [%s] failed to filter: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); return NULL; } } *length_r = length; return data; }
/** * Obtains the next chunk from the music pipe, optionally applies * cross-fading, and sends it to all audio outputs. * * @return true on success, false on error (playback will be stopped) */ static bool play_next_chunk(struct player *player) { struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; if (!audio_output_all_wait(pc, 64)) /* the output pipe is still large enough, don't send another chunk */ return true; unsigned cross_fade_position; struct music_chunk *chunk = NULL; if (player->xfade == XFADE_ENABLED && player_dc_at_next_song(player) && (cross_fade_position = music_pipe_size(player->pipe)) <= player->cross_fade_chunks) { /* perform cross fade */ struct music_chunk *other_chunk = music_pipe_shift(dc->pipe); if (!player->cross_fading) { /* beginning of the cross fade - adjust crossFadeChunks which might be bigger than the remaining number of chunks in the old song */ player->cross_fade_chunks = cross_fade_position; player->cross_fading = true; } if (other_chunk != NULL) { chunk = music_pipe_shift(player->pipe); assert(chunk != NULL); assert(chunk->other == NULL); /* don't send the tags of the new song (which is being faded in) yet; postpone it until the current song is faded out */ player->cross_fade_tag = tag_merge_replace(player->cross_fade_tag, other_chunk->tag); other_chunk->tag = NULL; if (isnan(pc->mixramp_delay_seconds)) { chunk->mix_ratio = ((float)cross_fade_position) / player->cross_fade_chunks; } else { chunk->mix_ratio = nan(""); } if (music_chunk_is_empty(other_chunk)) { /* the "other" chunk was a music_chunk which had only a tag, but no music data - we cannot cross-fade that; but since this happens only at the beginning of the new song, we can easily recover by throwing it away now */ music_buffer_return(player_buffer, other_chunk); other_chunk = NULL; } chunk->other = other_chunk; } else { /* there are not enough decoded chunks yet */ decoder_lock(dc); if (decoder_is_idle(dc)) { /* the decoder isn't running, abort cross fading */ decoder_unlock(dc); player->xfade = XFADE_DISABLED; } else { /* wait for the decoder */ decoder_signal(dc); player_wait_decoder(pc, dc); decoder_unlock(dc); return true; } } } if (chunk == NULL) chunk = music_pipe_shift(player->pipe); assert(chunk != NULL); /* insert the postponed tag if cross-fading is finished */ if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { chunk->tag = tag_merge_replace(chunk->tag, player->cross_fade_tag); player->cross_fade_tag = NULL; } /* play the current chunk */ if (!play_chunk(player->pc, player->song, chunk, &player->play_audio_format)) { music_buffer_return(player_buffer, chunk); player_lock(pc); pc->error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ pc->state = PLAYER_STATE_PAUSE; player->paused = true; player_unlock(pc); return false; } /* this formula should prevent that the decoder gets woken up with each chunk; it is more efficient to make it decode a larger block at a time */ decoder_lock(dc); if (!decoder_is_idle(dc) && music_pipe_size(dc->pipe) <= (pc->buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) decoder_signal(dc); decoder_unlock(dc); return true; }
static bool ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) { const char *data = chunk->data; size_t size = chunk->length; GError *error = NULL; assert(ao != NULL); assert(ao->filter != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); assert(size % audio_format_frame_size(&ao->in_audio_format) == 0); if (chunk->tag != NULL) { g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag); g_mutex_lock(ao->mutex); } /* update replay gain */ if (ao->replay_gain_filter != NULL && chunk->replay_gain_serial != ao->replay_gain_serial) { replay_gain_filter_set_info(ao->replay_gain_filter, chunk->replay_gain_serial != 0 ? &chunk->replay_gain_info : NULL); ao->replay_gain_serial = chunk->replay_gain_serial; } if (size == 0) return true; data = filter_filter(ao->filter, data, size, &size, &error); if (data == NULL) { g_warning("\"%s\" [%s] failed to filter: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); ao_close(ao, false); /* don't automatically reopen this device for 10 seconds */ ao->fail_timer = g_timer_new(); return false; } while (size > 0 && ao->command == AO_COMMAND_NONE) { size_t nbytes; g_mutex_unlock(ao->mutex); nbytes = ao_plugin_play(ao->plugin, ao->data, data, size, &error); g_mutex_lock(ao->mutex); if (nbytes == 0) { /* play()==0 means failure */ g_warning("\"%s\" [%s] failed to play: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); ao_close(ao, false); /* don't automatically reopen this device for 10 seconds */ ao->fail_timer = g_timer_new(); return false; } assert(nbytes <= size); assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); data += nbytes; size -= nbytes; } return true; }