bluez5: allow codecs to produce multiple packets from same data

Codecs may need to fragment a single encoder frame across multiple
packets that are sent consecutively.

Allow codec->encode() to set need_flush=NEED_FLUSH_FRAGMENT, so that
sink should immediately call start_encode + encode with NULL input data,
to produce the next packet.

Previously, other return values than need_flush=1 were unused, so no
need to bump codec ABI for this.
This commit is contained in:
Pauli Virtanen 2022-04-30 20:54:32 +03:00
parent 0cab700c06
commit 3d4eafcb0f
7 changed files with 66 additions and 6 deletions

View file

@ -493,7 +493,7 @@ static int codec_encode(void *data,
return -EINVAL; return -EINVAL;
*dst_out = out_args.numOutBytes; *dst_out = out_args.numOutBytes;
*need_flush = 1; *need_flush = NEED_FLUSH_ALL;
/* RFC6416: It is set to 1 to indicate that the RTP packet contains a complete /* RFC6416: It is set to 1 to indicate that the RTP packet contains a complete
* audioMuxElement or the last fragment of an audioMuxElement */ * audioMuxElement or the last fragment of an audioMuxElement */

View file

@ -404,7 +404,7 @@ static int codec_encode(void *data,
avail_dst_size = (this->max_frames - this->frame_count) * this->frame_length; avail_dst_size = (this->max_frames - this->frame_count) * this->frame_length;
if (SPA_UNLIKELY(dst_size < avail_dst_size)) { if (SPA_UNLIKELY(dst_size < avail_dst_size)) {
*need_flush = 1; *need_flush = NEED_FLUSH_ALL;
return 0; return 0;
} }
@ -414,7 +414,7 @@ static int codec_encode(void *data,
return -EINVAL; return -EINVAL;
this->frame_count += *dst_out / this->frame_length; this->frame_count += *dst_out / this->frame_length;
*need_flush = this->frame_count >= this->max_frames; *need_flush = (this->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO;
return res; return res;
} }

View file

@ -316,7 +316,7 @@ static int codec_encode(void *data,
} }
this->frame_count += res / this->codesize; this->frame_count += res / this->codesize;
*need_flush = this->frame_count >= this->max_frames; *need_flush = (this->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO;
return res; return res;
} }

View file

@ -565,7 +565,7 @@ static int codec_encode(void *data,
*dst_out = dst_used; *dst_out = dst_used;
this->payload->frame_count += frame_num; this->payload->frame_count += frame_num;
*need_flush = this->payload->frame_count > 0; *need_flush = (this->payload->frame_count > 0) ? NEED_FLUSH_ALL : NEED_FLUSH_NO;
return src_used; return src_used;
} }

View file

@ -605,7 +605,7 @@ static int codec_encode(void *data,
spa_assert(res == this->codesize); spa_assert(res == this->codesize);
this->payload->frame_count += res / this->codesize; this->payload->frame_count += res / this->codesize;
*need_flush = this->payload->frame_count >= this->max_frames; *need_flush = (this->payload->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO;
return res; return res;
} }

View file

@ -66,6 +66,12 @@ extern const char *codec_plugin_factory_name;
#define A2DP_CODEC_DEFAULT_RATE 48000 #define A2DP_CODEC_DEFAULT_RATE 48000
#define A2DP_CODEC_DEFAULT_CHANNELS 2 #define A2DP_CODEC_DEFAULT_CHANNELS 2
enum {
NEED_FLUSH_NO = 0,
NEED_FLUSH_ALL = 1,
NEED_FLUSH_FRAGMENT = 2,
};
struct a2dp_codec_audio_info { struct a2dp_codec_audio_info {
uint32_t rate; uint32_t rate;
uint32_t channels; uint32_t channels;

View file

@ -157,6 +157,8 @@ struct impl {
struct spa_audio_info codec_format; struct spa_audio_info codec_format;
int need_flush; int need_flush;
bool fragment;
uint64_t fragment_timeout;
uint32_t block_size; uint32_t block_size;
uint8_t buffer[BUFFER_SIZE]; uint8_t buffer[BUFFER_SIZE];
uint32_t buffer_used; uint32_t buffer_used;
@ -436,6 +438,7 @@ static int reset_buffer(struct impl *this)
} }
this->need_flush = 0; this->need_flush = 0;
this->frame_count = 0; this->frame_count = 0;
this->fragment = false;
this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer_used = this->codec->start_encode(this->codec_data,
this->buffer, sizeof(this->buffer), this->buffer, sizeof(this->buffer),
this->seqnum++, this->timestamp); this->seqnum++, this->timestamp);
@ -533,6 +536,37 @@ static int encode_buffer(struct impl *this, const void *data, uint32_t size)
return processed; return processed;
} }
static int encode_fragment(struct impl *this)
{
int res;
size_t out_encoded;
struct port *port = &this->port;
spa_log_trace(this->log, "%p: encode fragment used %d, %d %d %d",
this, this->buffer_used, port->frame_size, this->block_size,
this->frame_count);
if (this->need_flush)
return 0;
res = this->codec->encode(this->codec_data,
NULL, 0,
this->buffer + this->buffer_used,
sizeof(this->buffer) - this->buffer_used,
&out_encoded, &this->need_flush);
if (res < 0)
return res;
if (res != 0)
return -EINVAL;
this->buffer_used += out_encoded;
spa_log_trace(this->log, "%p: processed fragment %zd used %d",
this, out_encoded, this->buffer_used);
return 0;
}
static int flush_buffer(struct impl *this) static int flush_buffer(struct impl *this)
{ {
spa_log_trace(this->log, "%p: used:%d block_size:%d", this, spa_log_trace(this->log, "%p: used:%d block_size:%d", this,
@ -596,6 +630,15 @@ static int flush_data(struct impl *this, uint64_t now_time)
total_frames = 0; total_frames = 0;
again: again:
written = 0; written = 0;
if (this->fragment && !this->need_flush) {
int res;
this->fragment = false;
if ((res = encode_fragment(this)) < 0) {
/* Error */
reset_buffer(this);
return res;
}
}
while (!spa_list_is_empty(&port->ready) && !this->need_flush) { while (!spa_list_is_empty(&port->ready) && !this->need_flush) {
uint8_t *src; uint8_t *src;
uint32_t n_bytes, n_frames; uint32_t n_bytes, n_frames;
@ -706,6 +749,17 @@ again:
uint64_t timeout = (quantum > max_excess) ? uint64_t timeout = (quantum > max_excess) ?
(packet_time * (quantum - max_excess) / quantum) : 0; (packet_time * (quantum - max_excess) / quantum) : 0;
if (this->need_flush == NEED_FLUSH_FRAGMENT) {
reset_buffer(this);
this->fragment = true;
this->fragment_timeout = (packet_samples > 0) ? timeout : this->fragment_timeout;
goto again;
}
if (this->fragment_timeout > 0) {
timeout = this->fragment_timeout;
this->fragment_timeout = 0;
}
reset_buffer(this); reset_buffer(this);
if (now_time - this->last_error > SPA_NSEC_PER_SEC) { if (now_time - this->last_error > SPA_NSEC_PER_SEC) {
if (get_transport_unused_size(this) == (int)this->fd_buffer_size) { if (get_transport_unused_size(this) == (int)this->fd_buffer_size) {