diff --git a/src/frame.c b/src/frame.c index 2b45024..9dc075a 100644 --- a/src/frame.c +++ b/src/frame.c @@ -26,8 +26,6 @@ #define PCI_AUDIO_FIXED_OPP 0x8D8D33 #define PCI_FIXED 0x3634CE -#define MAX_AUDIO_PACKETS 64 - typedef struct { unsigned int codec; @@ -288,6 +286,32 @@ static unsigned int calc_lc_bits(frame_header_t *hdr) } } +static unsigned int calc_avg_packets(frame_header_t *hdr) +{ + switch(hdr->codec) + { + case 0: + return 32; + case 1: + case 2: + case 3: + if (hdr->stream_id == 0) + return 4; + else + return 32; + case 10: + if (hdr->stream_id == 0) + return 32; + else + return 4; + case 13: + return 4; + default: + log_warn("unknown codec field (%d)", hdr->codec); + return 32; + } +} + static unsigned int parse_location(uint8_t *buf, unsigned int lc_bits, unsigned int i) { if (lc_bits == 16) @@ -337,7 +361,7 @@ static void aas_push(frame_t *st, uint8_t* psd, unsigned int length, logical_cha else { // remove protocol and fcs fields - input_aas_push(st->input, psd + 1, length - 3); + output_aas_push(st->input->output, psd + 1, length - 3); } } @@ -503,7 +527,7 @@ void frame_process(frame_t *st, size_t length, logical_channel_t lc) while (offset < audio_end - RS_CODEWORD_LEN) { unsigned int start = offset; - unsigned int j, lc_bits, loc_bytes, prog; + unsigned int j, lc_bits, loc_bytes, prog, avg, seq; unsigned short locations[MAX_AUDIO_PACKETS]; frame_header_t hdr = {0}; hef_t hef = {0}; @@ -535,6 +559,10 @@ void frame_process(frame_t *st, size_t length, logical_channel_t lc) if (hdr.hef) offset += parse_hef(st->buffer + offset, audio_end - offset, &hef); prog = hef.prog_num; + avg = calc_avg_packets(&hdr); + seq = (hdr.seq - hdr.pfirst) % MAX_AUDIO_PACKETS; + + output_align(st->input->output, prog, hdr.stream_id, hdr.pdu_seq, hdr.latency, avg, seq, hdr.nop); parse_hdlc(st, aas_push, st->psd_buf[prog], &st->psd_idx[prog], MAX_AAS_LEN, st->buffer + offset, start + hdr.la_location + 1 - offset, lc); offset = start + hdr.la_location + 1; @@ -555,7 +583,7 @@ void frame_process(frame_t *st, size_t length, logical_channel_t lc) if (crc == 0) { memcpy(&st->pdu[prog][hdr.stream_id][idx], st->buffer + offset, cnt); - input_pdu_push(st->input, st->pdu[prog][hdr.stream_id], cnt + idx, prog, hdr.stream_id); + output_push(st->input->output, st->pdu[prog][hdr.stream_id], cnt + idx, prog, hdr.stream_id, seq); } st->pdu_idx[prog][hdr.stream_id] = 0; } @@ -576,11 +604,12 @@ void frame_process(frame_t *st, size_t length, logical_channel_t lc) { if (crc == 0) { - input_pdu_push(st->input, st->buffer + offset, cnt, prog, hdr.stream_id); + output_push(st->input->output, st->buffer + offset, cnt, prog, hdr.stream_id, seq); } } offset += cnt + 1; + seq = (seq + 1) % MAX_AUDIO_PACKETS; } } diff --git a/src/frame.h b/src/frame.h index 8b2226c..6ec193f 100644 --- a/src/frame.h +++ b/src/frame.h @@ -2,6 +2,7 @@ #include "defines.h" +#define MAX_AUDIO_PACKETS 64 #define MAX_AAS_LEN 8212 #define RS_BLOCK_LEN 255 #define RS_CODEWORD_LEN 96 diff --git a/src/input.c b/src/input.c index c781bd3..18d1458 100644 --- a/src/input.c +++ b/src/input.c @@ -38,28 +38,32 @@ static float decim_taps[] = { -0.00410953676328063 }; -static void input_push_to_acquire(input_t *st) +static unsigned int input_push_to_acquire(input_t *st) { + unsigned int used = 0; + if (st->skip) { if (st->skip > st->avail - st->used) { + used += st->avail - st->used; st->skip -= st->avail - st->used; st->used = st->avail; } else { + used += st->skip; st->used += st->skip; st->skip = 0; } } - st->used += acquire_push(&st->acq, &st->buffer[st->used], st->avail - st->used); -} + unsigned int needed = acquire_push(&st->acq, &st->buffer[st->used], st->avail - st->used); -void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id) -{ - output_push(st->output, pdu, len, program, stream_id); + st->used += needed; + used += needed; + + return used; } void input_set_skip(input_t *st, unsigned int skip) @@ -139,12 +143,13 @@ int input_shift(input_t *st, unsigned int cnt) return 0; } -void input_push(input_t *st) +void input_push(input_t *st, unsigned int pos) { while (st->avail - st->used >= (st->radio->mode == NRSC5_MODE_FM ? FFTCP_FM : FFTCP_AM)) { - input_push_to_acquire(st); + unsigned int pushed = input_push_to_acquire(st); acquire_process(&st->acq); + output_advance_elastic(st->output, (int)st->used - (int)pushed - (int)pos, pushed); } } @@ -164,6 +169,8 @@ void input_push_cu8(input_t *st, const uint8_t *buf, uint32_t len) if (input_shift(st, len / 4) != 0) return; + unsigned int pos = st->avail; + for (i = 0; i < len; i += 4) { cint16_t x[2]; @@ -201,7 +208,8 @@ void input_push_cu8(input_t *st, const uint8_t *buf, uint32_t len) } } - input_push(st); + input_push(st, pos); + output_advance(st->output, len / 4); } void input_push_cs16(input_t *st, const int16_t *buf, uint32_t len) @@ -211,10 +219,13 @@ void input_push_cs16(input_t *st, const int16_t *buf, uint32_t len) if (input_shift(st, len / 2) != 0) return; + unsigned int pos = st->avail; + memcpy(&st->buffer[st->avail], buf, len * sizeof(int16_t)); st->avail += len / 2; - input_push(st); + input_push(st, pos); + output_advance(st->output, len / 2); } void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *arg) @@ -292,9 +303,4 @@ void input_set_sync_state(input_t *st, unsigned int new_state) } st->sync_state = new_state; -} - -void input_aas_push(input_t *st, uint8_t *psd, unsigned int len) -{ - output_aas_push(st->output, psd, len); -} +} \ No newline at end of file diff --git a/src/input.h b/src/input.h index 50f5716..3bca60d 100644 --- a/src/input.h +++ b/src/input.h @@ -61,5 +61,3 @@ void input_push_cu8(input_t *st, const uint8_t *buf, uint32_t len); void input_push_cs16(input_t *st, const int16_t *buf, uint32_t len); void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *); void input_set_skip(input_t *st, unsigned int skip); -void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id); -void input_aas_push(input_t *st, uint8_t *psd, unsigned int len); diff --git a/src/main.c b/src/main.c index f5170ce..2128db5 100644 --- a/src/main.c +++ b/src/main.c @@ -41,13 +41,13 @@ #include "log.h" #define AUDIO_BUFFERS 128 -#define AUDIO_THRESHOLD 40 -#define AUDIO_DATA_LENGTH 8192 +#define MAX_AUDIO_DATA_LENGTH 31072 typedef struct buffer_t { struct buffer_t *next; // The samples are signed 16-bit integers, but ao_play requires a char buffer. - char data[AUDIO_DATA_LENGTH]; + char data[MAX_AUDIO_DATA_LENGTH]; + size_t count; } audio_buffer_t; typedef struct { @@ -143,8 +143,9 @@ static void push_audio_buffer(state_t *st, unsigned int program, const int16_t * st->free = b->next; pthread_mutex_unlock(&st->mutex); - assert(AUDIO_DATA_LENGTH == count * sizeof(data[0])); + assert(MAX_AUDIO_DATA_LENGTH >= count * sizeof(data[0])); memcpy(b->data, data, count * sizeof(data[0])); + b->count = count; pthread_mutex_lock(&st->mutex); if (program != st->program) @@ -161,9 +162,6 @@ static void push_audio_buffer(state_t *st, unsigned int program, const int16_t * st->head = b; st->tail = b; - if (st->audio_ready < AUDIO_THRESHOLD) - st->audio_ready++; - pthread_cond_signal(&st->cond); unlock: @@ -802,7 +800,7 @@ int main(int argc, char *argv[]) audio_buffer_t *b; pthread_mutex_lock(&st->mutex); - while (!st->done && (st->head == NULL || st->audio_ready < AUDIO_THRESHOLD)) + while (!st->done && (st->head == NULL)) pthread_cond_wait(&st->cond, &st->mutex); // exit once done and no more audio buffers @@ -819,7 +817,7 @@ int main(int argc, char *argv[]) st->tail = NULL; pthread_mutex_unlock(&st->mutex); - ao_play(st->dev, b->data, sizeof(b->data)); + ao_play(st->dev, b->data, b->count * sizeof(int16_t)); pthread_mutex_lock(&st->mutex); // add to free list diff --git a/src/output.c b/src/output.c index 160f89e..7f08142 100644 --- a/src/output.c +++ b/src/output.c @@ -27,30 +27,317 @@ #include "private.h" #include "unicode.h" -void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id) +#define RADIO_FRAME_SAMPLES_FM (NRSC5_AUDIO_FRAME_SAMPLES * 135 / 8) +#define RADIO_FRAME_SAMPLES_AM (NRSC5_AUDIO_FRAME_SAMPLES * 135 / 256) + +static unsigned int average_acquire_samples(output_t *st, elastic_buffer_t* dec) +{ + return dec->avg * (st->radio->mode == NRSC5_MODE_FM ? RADIO_FRAME_SAMPLES_FM : RADIO_FRAME_SAMPLES_AM); +} + +static unsigned int compute_forward_sequence_position(elastic_buffer_t *elastic, unsigned int seq) +{ + return (seq - elastic->ptr[elastic->write].seq) % MAX_AUDIO_PACKETS; +} + +static void elastic_realign_forward(elastic_buffer_t *elastic, unsigned int forward, unsigned int pdu_seq, unsigned int avg, unsigned int seq) +{ + elastic->write = (elastic->write + forward) % elastic->size; + elastic->ptr[elastic->write].seq = seq; + + unsigned int offset = pdu_seq * avg; + if (((offset + 64 - seq) % MAX_AUDIO_PACKETS) < 32) + offset = (offset + 32) % MAX_AUDIO_PACKETS; + + elastic->read = (elastic->write - elastic->delay - seq + offset) % elastic->size; +} + +static unsigned int elastic_writable(elastic_buffer_t *elastic) { - nrsc5_report_hdc(st->radio, program, pkt, len); + if (elastic->read > elastic->write) + return (elastic->read - elastic->write) - 1; + else + return elastic->size - (elastic->write - elastic->read) - 1; +} + +static void elastic_decode_packet(output_t *st, unsigned int program, int16_t** audio, unsigned int* frames) +{ + decoder_t *dec = &st->decoder[program]; + elastic_buffer_t *elastic = &dec->elastic_buffer; + + if (elastic->ptr[elastic->read].size != 0) + { + nrsc5_report_hdc(st->radio, program, elastic->ptr[elastic->read].data, elastic->ptr[elastic->read].size); + +#ifdef USE_FAAD2 + NeAACDecFrameInfo info; + void *buffer; + + if (!dec->aacdec) + { + unsigned long samprate = 22050; + NeAACDecInitHDC(&dec->aacdec, &samprate); + } + + buffer = NeAACDecDecode(dec->aacdec, &info, elastic->ptr[elastic->read].data, + elastic->ptr[elastic->read].size); + if (info.error > 0) + log_error("Decode error: %s", NeAACDecGetErrorMessage(info.error)); + + if (info.error > 0 || info.samples == 0) + { + *audio = st->silence; + *frames = AUDIO_FRAME_LENGTH; + } + else + { + assert(info.samples == AUDIO_FRAME_LENGTH); + *audio = buffer; + *frames = info.samples; + } + } + else + { + *audio = st->silence; + *frames = AUDIO_FRAME_LENGTH; + + // Reset decoder. Missing packets. + if (dec->aacdec) + { + NeAACDecClose(dec->aacdec); + dec->aacdec = NULL; + } +#endif + } + + elastic->ptr[elastic->read].size = 0; + elastic->read = (elastic->read + 1) % elastic->size; +} + +void output_align(output_t *st, unsigned int program, unsigned int stream_id, unsigned int pdu_seq, unsigned int latency, unsigned int avg, unsigned int seq, unsigned int nop) +{ + decoder_t *dec = &st->decoder[program]; + elastic_buffer_t *elastic = &dec->elastic_buffer; + unsigned int forward; if (stream_id != 0) return; // TODO: Process enhanced stream -#ifdef USE_FAAD2 - void *buffer; - NeAACDecFrameInfo info; + elastic->latency = latency * 2; + elastic->avg = avg; - if (!st->aacdec[program]) + // Create Elastic buffer + if (!elastic->ptr) { - unsigned long samprate = 22050; - NeAACDecInitHDC(&st->aacdec[program], &samprate); + // Buffer Diagram: ||delay|| + ||64|| + ||delay|| + elastic->delay = elastic->latency; + elastic->size = (elastic->delay * 2) + MAX_AUDIO_PACKETS; + elastic->ptr = malloc(elastic->size * sizeof(*elastic->ptr)); + + for (int i = 0; i < elastic->size; i++) + { + elastic->ptr[i].size = 0; + elastic->ptr[i].seq = -1; + } + + // Startup the buffer + elastic->write = elastic->delay; + + // Startup the clock + elastic->clock = average_acquire_samples(st, elastic); + + // Align Writer (buffer->delay + seq) & Reader + elastic_realign_forward(elastic, seq, pdu_seq, avg, seq); + + log_debug("Elastic buffer created. Program: %d, Size %d bytes, Read %d pos, Write: %d pos", program, elastic->size, elastic->read, elastic->write); } - buffer = NeAACDecDecode(st->aacdec[program], &info, pkt, len); - if (info.error > 0) - log_error("Decode error: %s", NeAACDecGetErrorMessage(info.error)); + if(!dec->output_buffer) + { + dec->output_buffer = malloc(OUTPUT_BUFFER_LENGTH * sizeof(*dec->output_buffer)); + memset(dec->output_buffer, 0, OUTPUT_BUFFER_LENGTH * sizeof(*dec->output_buffer)); + + // FFT decode delay + dec->write = (st->radio->mode == NRSC5_MODE_FM ? FFTCP_FM : FFTCP_AM) * 8 / 135; + } - if (info.error == 0 && info.samples > 0) - nrsc5_report_audio(st->radio, program, buffer, info.samples); + // Re-sync (lost-synchronization with reader and writer) + forward = compute_forward_sequence_position(elastic, seq); + if (elastic_writable(elastic) < forward + nop) + { + elastic_realign_forward(elastic, forward, pdu_seq, avg, seq); + log_debug("Elastic buffer realigned. Program: %d, Read %d pos, Write: %d pos", program, elastic->read, elastic->write); + } +} + +static unsigned int output_buffer_writeable(decoder_t *st) +{ + if (st->read > st->write) + return (st->read - st->write) - 1; + else + return OUTPUT_BUFFER_LENGTH - (st->write - st->read) - 1; +} + +unsigned int output_buffer_available(decoder_t *st) +{ + if (st->write >= st->read) + return st->write - st->read; + else + return OUTPUT_BUFFER_LENGTH - (st->read - st->write); +} + +static void output_buffer_write(decoder_t *dec, int16_t *buffer, unsigned int samples) +{ + if (output_buffer_writeable(dec) < samples) + { + log_error("Internal writer clock bug. Full of samples"); + return; + } + + if (dec->write + samples > OUTPUT_BUFFER_LENGTH) + { + unsigned int len = OUTPUT_BUFFER_LENGTH - dec->write; + memcpy(dec->output_buffer + dec->write, buffer, len * sizeof(*buffer)); + memcpy(dec->output_buffer, buffer + len, (samples - len) * sizeof(*buffer)); + dec->write = samples - len; + } + else + { + memcpy(dec->output_buffer + dec->write, buffer, samples * sizeof(*buffer)); + dec->write = (dec->write + samples) % OUTPUT_BUFFER_LENGTH; + } +} + +static void output_read_buffer(decoder_t *dec, int16_t *buffer, unsigned int samples) +{ + if (dec->read + samples > OUTPUT_BUFFER_LENGTH) + { + unsigned int first = OUTPUT_BUFFER_LENGTH - dec->read; + memcpy(buffer, &dec->output_buffer[dec->read], first * sizeof(*buffer)); + memcpy(&buffer[first], dec->output_buffer, (samples - first) * sizeof(*buffer)); + dec->read = samples - first; + } + else + { + memcpy(buffer, &dec->output_buffer[dec->read], samples * sizeof(*buffer)); + dec->read = (dec->read + samples) % OUTPUT_BUFFER_LENGTH; + } +} + +void output_advance_elastic(output_t *st, int pos, unsigned int used) +{ + for (int i = 0; i < MAX_PROGRAMS; i++) + { + decoder_t *dec = &st->decoder[i]; + elastic_buffer_t *elastic = &dec->elastic_buffer; + unsigned int sample_avg = average_acquire_samples(st, elastic); + + // Skip if no buffer + if (!elastic->ptr) + continue; + + if (elastic->pos == -1) + elastic->pos = pos; + + // Packet clock + elastic->clock += (int)used; + + // Decode packets based on average + while (elastic->clock >= (int)sample_avg) + { + int16_t *audio; + unsigned int decoded_frames; + + for (int j = 0; j < elastic->avg; j++) + { + elastic_decode_packet(st, i, &audio, &decoded_frames); +#ifdef USE_FAAD2 + output_buffer_write(dec, audio, decoded_frames); #endif + } + elastic->clock -= (int)sample_avg; + } + + } +} + +void output_advance(output_t *st, unsigned int len) +{ + for (int i = 0; i < MAX_PROGRAMS; i++) + { + decoder_t *dec = &st->decoder[i]; + elastic_buffer_t *elastic = &dec->elastic_buffer; + + // Skip if no buffer + if (elastic->write == 0) + continue; + + unsigned int hd_samples; + unsigned int delay_samples = 0; + + // Program started in the middle of the sample. + // Insert silence to makeup for it. It takes time to generate samples + if (elastic->pos > 0) + { + hd_samples = (len - elastic->pos); + delay_samples += (len - hd_samples); + } + else + { + hd_samples = len; + } + + unsigned int iq_upper = (hd_samples * 8) + dec->leftover; + unsigned int audio_frames = (iq_upper / 135) * AUDIO_FRAME_CHANNELS; + unsigned int silence_frames = (delay_samples * 8 / 135) * AUDIO_FRAME_CHANNELS; + unsigned int frame_len = audio_frames + silence_frames; + + int16_t* audio_frame = malloc(frame_len * sizeof(*audio_frame)); + memset(audio_frame, 0, silence_frames * sizeof(*audio_frame)); + + if (output_buffer_available(dec) < audio_frames) + { + log_error("Internal reader clock bug. Missing samples. " + "Requested: %d Available: %d", audio_frames, output_buffer_available(dec)); + audio_frames = output_buffer_available(dec); + } + + if (audio_frames == 0) + return; + + output_read_buffer(dec, audio_frame + silence_frames, audio_frames); + nrsc5_report_audio(st->radio, i, audio_frame, frame_len); + + free(audio_frame); + + // Reset + elastic->pos = -1; + dec->leftover = iq_upper % 135; + } +} + +void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id, unsigned int seq) +{ + decoder_t *dec = &st->decoder[program]; + elastic_buffer_t *elastic = &dec->elastic_buffer; + + if (stream_id != 0) + return; // TODO: Process enhanced stream + + if (elastic_writable(elastic) == 0) + { + log_error("elastic buffer full. skipped packet. bug?"); + return; + } + + unsigned int forward = compute_forward_sequence_position(elastic, seq); + unsigned int pos = (elastic->write + forward) % elastic->size; + + memcpy(elastic->ptr[pos].data, pkt, len); + elastic->ptr[pos].size = len; + elastic->ptr[pos].seq = seq; + + elastic->write = pos; } static void aas_free_lot(aas_file_t *file) @@ -95,9 +382,30 @@ void output_reset(output_t *st) #ifdef USE_FAAD2 for (int i = 0; i < MAX_PROGRAMS; i++) { - if (st->aacdec[i]) - NeAACDecClose(st->aacdec[i]); - st->aacdec[i] = NULL; + decoder_t *dec = &st->decoder[i]; + elastic_buffer_t *buffer = &dec->elastic_buffer; + + if (dec->aacdec) + NeAACDecClose(dec->aacdec); + dec->aacdec = NULL; + + buffer->write = 0; + buffer->read = 0; + buffer->clock = 0; + buffer->pos = -1; + + if (buffer->ptr) + free(buffer->ptr); + buffer->ptr = NULL; + + dec->leftover = 0; + dec->write = 0; + dec->read = 0; + dec->delay = 0; + + if (dec->output_buffer) + free(dec->output_buffer); + dec->output_buffer = NULL; } #endif } @@ -105,9 +413,12 @@ void output_reset(output_t *st) void output_init(output_t *st, nrsc5_t *radio) { st->radio = radio; + #ifdef USE_FAAD2 for (int i = 0; i < MAX_PROGRAMS; i++) - st->aacdec[i] = NULL; + st->decoder[i].aacdec = NULL; + + memset(st->silence, 0, sizeof(st->silence)); #endif memset(st->ports, 0, sizeof(st->ports)); diff --git a/src/output.h b/src/output.h index b99e975..e7c1b3d 100644 --- a/src/output.h +++ b/src/output.h @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "frame.h" #include @@ -8,7 +9,6 @@ #include #endif -#define AUDIO_FRAME_BYTES 8192 #define MAX_PORTS 32 #define MAX_SIG_SERVICES 8 #define MAX_SIG_COMPONENTS 8 @@ -17,6 +17,11 @@ #define MAX_FILE_BYTES 65536 #define MAX_LOT_FRAGMENTS (MAX_FILE_BYTES / LOT_FRAGMENT_SIZE) +#define AUDIO_FRAME_CHANNELS 2 +#define AUDIO_FRAME_LENGTH (NRSC5_AUDIO_FRAME_SAMPLES * AUDIO_FRAME_CHANNELS) + +#define OUTPUT_BUFFER_LENGTH (64 * AUDIO_FRAME_LENGTH) + #define AAS_TYPE_STREAM 0 #define AAS_TYPE_PACKET 1 #define AAS_TYPE_LOT 3 @@ -85,17 +90,50 @@ typedef struct sig_component_t component[MAX_SIG_COMPONENTS]; } sig_service_t; +typedef struct +{ + unsigned int seq; + unsigned int size; + uint8_t data[MAX_PDU_LEN]; +} packet_t; + +typedef struct +{ + packet_t *ptr; + + unsigned int size, read, write; + unsigned int latency, avg, delay; + + unsigned int clock; + int pos; +} elastic_buffer_t; + +typedef struct +{ + NeAACDecHandle aacdec; + elastic_buffer_t elastic_buffer; + + int16_t* output_buffer; + unsigned int write, read, leftover, delay; +} decoder_t; + typedef struct { nrsc5_t *radio; -#ifdef HAVE_FAAD2 - NeAACDecHandle aacdec[MAX_PROGRAMS]; -#endif aas_port_t ports[MAX_PORTS]; sig_service_t services[MAX_SIG_SERVICES]; + +#ifdef HAVE_FAAD2 + decoder_t decoder[MAX_PROGRAMS]; + + int16_t silence[AUDIO_FRAME_LENGTH]; +#endif } output_t; -void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id); +void output_align(output_t *st, unsigned int program, unsigned int stream_id, unsigned int pdu_seq, unsigned int latency, unsigned int avg, unsigned int seq, unsigned int nop); +void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id, unsigned int seq); +void output_advance_elastic(output_t *st, int pos, unsigned int used); +void output_advance(output_t *st, unsigned int len); void output_begin(output_t *st); void output_reset(output_t *st); void output_init(output_t *st, nrsc5_t *);