uint64_t libavsmash_get_pcm_audio_samples
(
    libavsmash_audio_decode_handler_t *adhp,
    libavsmash_audio_output_handler_t *aohp,
    void                              *buf,
    int64_t                            start,
    int64_t                            wanted_length
)
{
    codec_configuration_t *config = &adhp->config;
    if( config->error )
        return 0;
    uint32_t               frame_number;
    uint64_t               output_length = 0;
    enum audio_output_flag output_flags;
    aohp->request_length = wanted_length;
    if( start > 0 && start == adhp->next_pcm_sample_number )
    {
        frame_number   = adhp->last_frame_number;
        output_flags   = AUDIO_OUTPUT_NO_FLAGS;
        output_length += output_pcm_samples_from_buffer( aohp, adhp->frame_buffer, (uint8_t **)&buf, &output_flags );
        if( output_flags & AUDIO_OUTPUT_ENOUGH )
            goto audio_out;
        if( adhp->packet.size <= 0 )
            ++frame_number;
        aohp->output_sample_offset = 0;
    }
    else
    {
        /* Seek audio stream. */
        if( flush_resampler_buffers( aohp->avr_ctx ) < 0 )
        {
            config->error = 1;
            if( config->lh.show_log )
                config->lh.show_log( &config->lh, LW_LOG_FATAL,
                                     "Failed to flush resampler buffers.\n"
                                     "It is recommended you reopen the file." );
            return 0;
        }
        libavsmash_flush_buffers( config );
        if( config->error )
            return 0;
        adhp->next_pcm_sample_number = 0;
        adhp->last_frame_number      = 0;
        uint64_t start_frame_pos;
        if( start >= 0 )
            start_frame_pos = start;
        else
        {
            uint64_t silence_length = -start;
            put_silence_audio_samples( (int)(silence_length * aohp->output_block_align), aohp->output_bits_per_sample == 8, (uint8_t **)&buf );
            output_length        += silence_length;
            aohp->request_length -= silence_length;
            start_frame_pos = 0;
        }
        start_frame_pos += aohp->skip_decoded_samples;
        frame_number = find_start_audio_frame( adhp, aohp->output_sample_rate, aohp->skip_decoded_samples, start_frame_pos, &aohp->output_sample_offset );
    }
    do
    {
        AVPacket *pkt = &adhp->packet;
        if( frame_number > adhp->frame_count )
        {
            if( config->delay_count )
            {
                /* Null packet */
                av_init_packet( pkt );
                pkt->data = NULL;
                pkt->size = 0;
                -- config->delay_count;
            }
            else
                goto audio_out;
        }
        else if( pkt->size <= 0 )
            /* Getting an audio packet must be after flushing all remaining samples in resampler's FIFO buffer. */
            while( get_sample( adhp->root, adhp->track_ID, frame_number, config, pkt ) == 2 )
                if( config->update_pending )
                    /* Update the decoder configuration. */
                    update_configuration( adhp->root, adhp->track_ID, config );
        /* Decode and output from an audio packet. */
        output_flags   = AUDIO_OUTPUT_NO_FLAGS;
        output_length += output_pcm_samples_from_packet( aohp, config->ctx, pkt, adhp->frame_buffer, (uint8_t **)&buf, &output_flags );
        if( output_flags & AUDIO_DECODER_DELAY )
            ++ config->delay_count;
        if( output_flags & AUDIO_RECONFIG_FAILURE )
        {
            config->error = 1;
            if( config->lh.show_log )
                config->lh.show_log( &config->lh, LW_LOG_FATAL,
                                     "Failed to reconfigure resampler.\n"
                                     "It is recommended you reopen the file." );
            goto audio_out;
        }
        if( output_flags & AUDIO_OUTPUT_ENOUGH )
            goto audio_out;
        ++frame_number;
    } while( 1 );
audio_out:
    adhp->next_pcm_sample_number = start + output_length;
    adhp->last_frame_number      = frame_number;
    return output_length;
}
uint64_t lwlibav_audio_get_pcm_samples
(
    lwlibav_audio_decode_handler_t *adhp,
    lwlibav_audio_output_handler_t *aohp,
    void                           *buf,
    int64_t                         start,
    int64_t                         wanted_length
)
{
    if( adhp->error )
        return 0;
    uint32_t               frame_number;
    uint32_t               rap_number      = 0;
    uint32_t               past_rap_number = 0;
    uint64_t               output_length   = 0;
    enum audio_output_flag output_flags    = AUDIO_OUTPUT_NO_FLAGS;
    AVPacket              *pkt       = &adhp->packet;
    AVPacket              *alter_pkt = &adhp->alter_packet;
    int                    already_gotten;
    aohp->request_length = wanted_length;
    if( start > 0 && start == adhp->next_pcm_sample_number )
    {
        frame_number   = adhp->last_frame_number;
        output_length += output_pcm_samples_from_buffer( aohp, adhp->frame_buffer, (uint8_t **)&buf, &output_flags );
        if( output_flags & AUDIO_OUTPUT_ENOUGH )
            goto audio_out;
        if( alter_pkt->size <= 0 )
            ++frame_number;
        aohp->output_sample_offset = 0;
        already_gotten             = 0;
    }
    else
    {
        /* Seek audio stream. */
        adhp->next_pcm_sample_number = 0;
        adhp->last_frame_number      = 0;
        /* Get frame_number. */
        uint64_t start_frame_pos;
        if( start >= 0 )
            start_frame_pos = start;
        else
        {
            uint64_t silence_length = -start;
            put_silence_audio_samples( (int)(silence_length * aohp->output_block_align), aohp->output_bits_per_sample == 8, (uint8_t **)&buf );
            output_length        += silence_length;
            aohp->request_length -= silence_length;
            start_frame_pos = 0;
        }
        frame_number = find_start_audio_frame( adhp, aohp->output_sample_rate, start_frame_pos, &aohp->output_sample_offset );
retry_seek:
        av_packet_unref( pkt );
        /* Flush audio resampler buffers. */
        if( flush_resampler_buffers( aohp->avr_ctx ) < 0 )
        {
            adhp->error = 1;
            lw_log_show( &adhp->lh, LW_LOG_FATAL,
                         "Failed to flush resampler buffers.\n"
                         "It is recommended you reopen the file." );
            return 0;
        }
        /* Flush audio decoder buffers. */
        lwlibav_extradata_handler_t *exhp = &adhp->exh;
        int extradata_index = adhp->frame_list[frame_number].extradata_index;
        if( extradata_index != exhp->current_index )
        {
            /* Update the extradata. */
            rap_number = get_audio_rap( adhp, frame_number );
            assert( rap_number != 0 );
            lwlibav_update_configuration( (lwlibav_decode_handler_t *)adhp, rap_number, extradata_index, 0 );
        }
        else
            lwlibav_flush_buffers( (lwlibav_decode_handler_t *)adhp );
        if( adhp->error )
            return 0;
        /* Seek and get a audio packet. */
        rap_number = seek_audio( adhp, frame_number, past_rap_number, pkt, output_flags != AUDIO_OUTPUT_NO_FLAGS ? adhp->frame_buffer : NULL );
        already_gotten = 1;
    }
    do
    {
        if( already_gotten )
        {
            already_gotten = 0;
            make_decodable_packet( alter_pkt, pkt );
        }
        else if( frame_number > adhp->frame_count )
        {
            av_packet_unref( pkt );
            if( adhp->exh.delay_count || !(output_flags & AUDIO_OUTPUT_ENOUGH) )
            {
                /* Null packet */
                av_init_packet( pkt );
                make_null_packet( pkt );
                *alter_pkt = *pkt;
                if( adhp->exh.delay_count )
                    adhp->exh.delay_count -= 1;
            }
            else
                goto audio_out;
        }
        else if( alter_pkt->size <= 0 )
        {
            /* Getting an audio packet must be after flushing all remaining samples in resampler's FIFO buffer. */
            lwlibav_get_av_frame( adhp->format, adhp->stream_index, frame_number, pkt );
            make_decodable_packet( alter_pkt, pkt );
        }
        /* Decode and output from an audio packet. */
        output_flags   = AUDIO_OUTPUT_NO_FLAGS;
        output_length += output_pcm_samples_from_packet( aohp, adhp->ctx, alter_pkt, adhp->frame_buffer, (uint8_t **)&buf, &output_flags );
        if( output_flags & AUDIO_DECODER_DELAY )
        {
            if( rap_number > 1 && (output_flags & AUDIO_DECODER_ERROR) )
            {
                /* Retry to seek from more past audio keyframe because libavformat might have failed seek.
                 * This operation occurs only at the first decoding time after seek. */
                past_rap_number = get_audio_rap( adhp, rap_number - 1 );
                if( past_rap_number
                 && past_rap_number < rap_number )
                    goto retry_seek;
            }
            ++ adhp->exh.delay_count;
        }
        else
            /* Disable seek retry. */
            rap_number = 0;
        if( output_flags & AUDIO_RECONFIG_FAILURE )
        {
            adhp->error = 1;
            lw_log_show( &adhp->lh, LW_LOG_FATAL,
                         "Failed to reconfigure resampler.\n"
                         "It is recommended you reopen the file." );
            goto audio_out;
        }
        if( output_flags & AUDIO_OUTPUT_ENOUGH )
            goto audio_out;
        if( output_flags & (AUDIO_DECODER_ERROR | AUDIO_DECODER_RECEIVED_PACKET) )
            ++frame_number;
    } while( 1 );
audio_out:
    adhp->next_pcm_sample_number = start + output_length;
    adhp->last_frame_number      = frame_number;
    return output_length;
}