/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / IETF RTP/RTSP/SDP sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <gpac/internal/ietf_dev.h>

#ifndef GPAC_DISABLE_STREAMING

#include <gpac/constants.h>

static void rtp_amr_flush(GP_RTPPacketizer *builder)
{
	u8 *hdr;
	u32 hdr_size;
	if (!builder->bytesInPacket) return;
	gf_bs_get_content(builder->pck_hdr, &hdr, &hdr_size);
	gf_bs_del(builder->pck_hdr);
	builder->pck_hdr = NULL;
	/*overwrite last frame F bit*/
	hdr[builder->last_au_sn] &= 0x7F;
	builder->OnData(builder->cbk_obj, hdr, hdr_size, GF_TRUE);
	gf_free(hdr);
	builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
	builder->bytesInPacket = 0;
	builder->last_au_sn = 0;
}

GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	u32 offset, rtp_ts, block_size;

	if (!data) {
		rtp_amr_flush(builder);
		return GF_OK;
	}

	rtp_ts = (u32) builder->sl_header.compositionTimeStamp;

	offset = 0;
	while (data_size>offset) {
		u8 ft = (data[offset] & 0x78) >> 3;
		u8 size;

		if (builder->rtp_payt == GF_RTP_PAYT_AMR_WB) {
			size = (u32)GF_AMR_WB_FRAME_SIZE[ft];
			block_size = 320;
		} else {
			size = (u32)GF_AMR_FRAME_SIZE[ft];
			block_size = 160;
		}

		/*packet full or too long*/
		if (builder->bytesInPacket + 1 + size > builder->Path_MTU)
			rtp_amr_flush(builder);

		/*need new*/
		if (!builder->bytesInPacket) {
			builder->rtp_header.TimeStamp = rtp_ts;
			builder->rtp_header.Marker = 0;	/*never set*/
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			gf_assert(builder->pck_hdr==NULL);

			/*always have header and TOC*/
			builder->pck_hdr = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
			/*CMR + res (all 0, no interleaving)*/
			gf_bs_write_int(builder->pck_hdr, ft, 4);
			gf_bs_write_int(builder->pck_hdr, 0, 4);
			builder->bytesInPacket = 1;
			/*no interleaving*/
		}

		/*F always to 1*/
		gf_bs_write_int(builder->pck_hdr, 1, 1);
		gf_bs_write_int(builder->pck_hdr, ft, 4);
		/*Q*/
		gf_bs_write_int(builder->pck_hdr, (data[offset] & 0x4) ? 1 : 0, 1);
		gf_bs_write_int(builder->pck_hdr, 0, 2);
		builder->bytesInPacket ++;

		/*remove frame type byte*/
		offset++;

		/*add frame data without rate_type byte header*/
		if (builder->OnDataReference) {
			builder->OnDataReference(builder->cbk_obj, size, offset);
		} else {
			builder->OnData(builder->cbk_obj, data+offset, size, GF_FALSE);
		}
		builder->last_au_sn++;
		builder->bytesInPacket += size;
		offset += size;
		rtp_ts += block_size;
		gf_assert(builder->bytesInPacket<=builder->Path_MTU);
		/*take care of aggregation, flush if needed*/
		if (builder->last_au_sn==builder->auh_size) rtp_amr_flush(builder);
	}
	return GF_OK;
}

static GFINLINE u8 qes_get_rate_size(u32 idx, const unsigned int *rates, const unsigned int nb_rates)
{
	u32 i;
	for (i=0; i<nb_rates; i++) {
		if (rates[2*i]==idx) return rates[2*i+1];
	}
	return 0;
}

GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	u32 offset, rtp_ts;
	u8 hdr;

	if (!data) {
		if (builder->bytesInPacket) builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
		builder->bytesInPacket = 0;
		builder->last_au_sn = 0;
		return GF_OK;
	}

	rtp_ts = (u32) builder->sl_header.compositionTimeStamp;


	offset = 0;
	while (data_size>offset) {
		u8 frame_type = data[offset];
		u8 size = qes_get_rate_size(frame_type, GF_QCELP_RATE_TO_SIZE, GF_QCELP_RATE_TO_SIZE_NB);
		/*reserved, not sent)*/
		if (frame_type>=5) {
			offset += size;
			continue;
		}
		/*packet full or too long*/
		if (builder->bytesInPacket + size > builder->Path_MTU) {
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
			builder->last_au_sn = 0;
		}

		/*need new*/
		if (!builder->bytesInPacket) {
			builder->rtp_header.TimeStamp = rtp_ts;
			builder->rtp_header.Marker = 0;	/*never set*/
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			hdr = 0;/*no interleaving*/
			builder->OnData(builder->cbk_obj, (char*)&hdr, 1, GF_FALSE);
			builder->bytesInPacket = 1;
		}
		if (builder->OnDataReference) {
			builder->OnDataReference(builder->cbk_obj, size, offset);
		} else {
			builder->OnData(builder->cbk_obj, data+offset, size, GF_FALSE);
		}
		builder->bytesInPacket += size;
		offset += size;
		rtp_ts += 160;
		gf_assert(builder->bytesInPacket<=builder->Path_MTU);

		/*take care of aggregation, flush if needed*/
		builder->last_au_sn++;
		if (builder->last_au_sn==builder->auh_size) {
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
			builder->last_au_sn = 0;
		}
	}
	return GF_OK;
}

static void rtp_evrc_smv_flush(GP_RTPPacketizer *builder)
{
	if (!builder->bytesInPacket) return;
	if (builder->auh_size>1) {
		u8 *hdr;
		u32 hdr_size;
		/*padding*/
		if (builder->last_au_sn % 2) gf_bs_write_int(builder->pck_hdr, 0, 4);
		gf_bs_get_content(builder->pck_hdr, &hdr, &hdr_size);
		gf_bs_del(builder->pck_hdr);
		builder->pck_hdr = NULL;
		/*overwrite count*/
		hdr[0] = 0;
		hdr[1] = builder->last_au_sn-1;/*MMM + frameCount-1*/
		builder->OnData(builder->cbk_obj, hdr, hdr_size, GF_TRUE);
		gf_free(hdr);
	}
	builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
	builder->bytesInPacket = 0;
	builder->last_au_sn = 0;
}

GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	u32 offset, rtp_ts;

	if (!data) {
		rtp_evrc_smv_flush(builder);
		return GF_OK;
	}

	rtp_ts = (u32) builder->sl_header.compositionTimeStamp;

	offset = 0;
	while (data_size>offset) {
		u8 frame_type = data[offset];
		u8 size = qes_get_rate_size(frame_type, GF_SMV_EVRC_RATE_TO_SIZE, GF_SMV_EVRC_RATE_TO_SIZE_NB);

		/*reserved, not sent)*/
		if (frame_type>=5) {
			offset += size;
			continue;
		}
		/*packet full or too long*/
		if (builder->bytesInPacket + size > builder->Path_MTU)
			rtp_evrc_smv_flush(builder);

		/*need new*/
		if (!builder->bytesInPacket) {
			builder->rtp_header.TimeStamp = rtp_ts;
			builder->rtp_header.Marker = 0;	/*never set*/
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			gf_assert(builder->pck_hdr==NULL);

			if (builder->auh_size>1) {
				builder->pck_hdr = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
				/*RRLLLNNN (all 0, no interleaving)*/
				gf_bs_write_u8(builder->pck_hdr, 0);
				/*MMM + count-1 : overridden when flushing*/
				gf_bs_write_u8(builder->pck_hdr, 0);
				builder->bytesInPacket = 2;
			}
		}

		/*bundle mode: cat rate byte to TOC, on 4 bits*/
		if (builder->auh_size>1) {
			gf_bs_write_int(builder->pck_hdr, data[offset], 4);
			if (!(builder->last_au_sn % 2)) builder->bytesInPacket += 1;
		}
		/*note that EVEN in header-free format the rate_type byte is removed*/
		offset++;
		size--;

		/*add frame data without rate_type byte header*/
		if (builder->OnDataReference) {
			builder->OnDataReference(builder->cbk_obj, size, offset);
		} else {
			builder->OnData(builder->cbk_obj, data+offset, size, GF_FALSE);
		}
		builder->last_au_sn++;
		builder->bytesInPacket += size;
		offset += size;
		rtp_ts += 160;
		gf_assert(builder->bytesInPacket<=builder->Path_MTU);
		/*take care of aggregation, flush if needed*/
		if (builder->last_au_sn==builder->auh_size) rtp_evrc_smv_flush(builder);
	}
	return GF_OK;
}

GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	u8 hdr[2];
	Bool Pbit;
	u32 offset, size, max_size;

	builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;

	/*the H263 hinter doesn't perform inter-sample concatenation*/
	if (!data) return GF_OK;

	Pbit = GF_TRUE;

	/*skip 16 0'ed bits of start code*/
	offset = 2;
	data_size -= 2;
	max_size = builder->Path_MTU - 2;

	while(data_size > 0) {
		GF_BitStream *bs;
		if(data_size > max_size) {
			size = max_size;
			builder->rtp_header.Marker = 0;
		} else {
			size = data_size;
			builder->rtp_header.Marker = 1;
		}

		data_size -= size;

		/*create new RTP Packet */
		builder->rtp_header.SequenceNumber += 1;
		builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);

		bs = gf_bs_new(hdr, 2, GF_BITSTREAM_WRITE);
		gf_bs_write_int(bs, 0, 5);
		gf_bs_write_int(bs, Pbit, 1);
		gf_bs_write_int(bs, 0, 10);
		gf_bs_del(bs);

		/*add header*/
		builder->OnData(builder->cbk_obj, (char*) hdr, 2, GF_TRUE);
		/*add payload*/
		if (builder->OnDataReference)
			builder->OnDataReference(builder->cbk_obj, size, offset);
		else
			builder->OnData(builder->cbk_obj, data + offset, size, GF_FALSE);

		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);

		offset += size;
		Pbit = GF_FALSE;
	}
	return GF_OK;
}

GF_Err gp_rtp_builder_do_mp2t(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	u32 offset, size, max_size;

	builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;

	if (!data) return GF_OK;

	max_size = builder->Path_MTU;
	offset = 0;
	while (data_size > 0) {
		if (data_size > max_size) {
			size = max_size / 188;
			size *= 188;
		} else {
			size = data_size;
		}

		data_size -= size;

		/*create new RTP Packet */
		builder->rtp_header.SequenceNumber += 1;
		builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);

		/*add payload*/
		if (builder->OnDataReference)
			builder->OnDataReference(builder->cbk_obj, size, offset);
		else
			builder->OnData(builder->cbk_obj, data + offset, size, GF_TRUE);

		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);

		offset += size;
	}
	return GF_OK;
}
GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex)
{
	GF_BitStream *bs;
	u8 *hdr;
	u32 samp_size, txt_size, pay_start, hdr_size, txt_done, cur_frag, nb_frag;
	Bool is_utf_16 = GF_FALSE;

	if (!data) {
		/*flush packet*/
		if (builder->bytesInPacket) {
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
		}
		return GF_OK;
	}
	if (data_size<2) return GF_NON_COMPLIANT_BITSTREAM;

	/*cfg packet*/
	txt_size = data[0];
	txt_size <<= 8;
	txt_size |= (unsigned char) data[1];
	/*remove BOM*/
	pay_start = 2;
	if (txt_size>2) {
		if (data_size<4) return GF_NON_COMPLIANT_BITSTREAM;
		/*seems 3GP only accepts BE UTF-16 (no LE, no UTF32)*/
		if (((u8) data[2]==(u8) 0xFE) && ((u8) data[3]==(u8) 0xFF)) {
			is_utf_16 = GF_TRUE;
			pay_start = 4;
			txt_size -= 2;
		}
	}
	samp_size = data_size - pay_start;

	/*if TTU does not fit in packet flush packet*/
	if (builder->bytesInPacket && (builder->bytesInPacket + 3 + 6 + samp_size > builder->Path_MTU)) {
		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
		builder->bytesInPacket = 0;
	}
	//we only deal with static SIDX
	descIndex += GF_RTP_TX3G_SIDX_OFFSET;

	/*first TTU in packet*/
	if (!builder->bytesInPacket) {
		builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
		builder->rtp_header.Marker = 1;
		builder->rtp_header.SequenceNumber += 1;
		builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
	}
	/*fits entirely*/
	if (builder->bytesInPacket + 3 + 6 + samp_size <= builder->Path_MTU) {
		bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
		gf_bs_write_int(bs, is_utf_16, 1);
		gf_bs_write_int(bs, 0, 4);
		gf_bs_write_int(bs, 1, 3);
		gf_bs_write_u16(bs, 8 + samp_size);
		gf_bs_write_u8(bs, descIndex);
		gf_bs_write_u24(bs, duration);
		gf_bs_write_u16(bs, txt_size);
		gf_bs_get_content(bs, &hdr, &hdr_size);
		gf_bs_del(bs);
		builder->OnData(builder->cbk_obj, (char *) hdr, hdr_size, GF_FALSE);
		builder->bytesInPacket += hdr_size;
		gf_free(hdr);

		if (txt_size) {
			if (builder->OnDataReference) {
				builder->OnDataReference(builder->cbk_obj, samp_size, pay_start);
			} else {
				builder->OnData(builder->cbk_obj, data + pay_start, samp_size, GF_FALSE);
			}
			builder->bytesInPacket += samp_size;
		}
		/*disable aggregation*/
		if (!(builder->flags & GP_RTP_PCK_USE_MULTI)) {
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
		}
		return GF_OK;
	}
	/*doesn't fit and already data, flush packet*/
	if (builder->bytesInPacket) {
		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
		builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
		/*split unit*/
		builder->rtp_header.Marker = 0;
		builder->rtp_header.SequenceNumber += 1;
		builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
		builder->bytesInPacket = 0;
	}
	/*write all type2 units (text only) - FIXME: split at char boundaries, NOT SUPPORTED YET*/
	txt_done = 0;
	nb_frag = 1;
	/*all fragments needed for Type2 units*/
	while (txt_done + (builder->Path_MTU-10) < txt_size) {
		txt_done += (builder->Path_MTU-10);
		nb_frag += 1;
	}
	/*all fragments needed for Type3/4 units*/
	txt_done = txt_size;
	while (txt_done + (builder->Path_MTU-7) < samp_size) {
		txt_done += (builder->Path_MTU-7);
		nb_frag += 1;
	}


	cur_frag = 0;
	txt_done = 0;
	while (txt_done<txt_size) {
		u32 size;
		if (txt_done + (builder->Path_MTU-10) < txt_size) {
			size = builder->Path_MTU-10;
		} else {
			size = txt_size - txt_done;
		}

		bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
		gf_bs_write_int(bs, is_utf_16, 1);
		gf_bs_write_int(bs, 0, 4);
		gf_bs_write_int(bs, 2, 3);
		gf_bs_write_u16(bs, 9 + size);
		gf_bs_write_int(bs, nb_frag, 4);
		gf_bs_write_int(bs, cur_frag, 4);
		gf_bs_write_u24(bs, duration);
		gf_bs_write_u8(bs, descIndex);
		/*SLEN is the full original length minus text len and BOM (put here for buffer allocation purposes)*/
		gf_bs_write_u16(bs, samp_size);
		gf_bs_get_content(bs, &hdr, &hdr_size);
		gf_bs_del(bs);
		builder->OnData(builder->cbk_obj, (char *) hdr, hdr_size, GF_FALSE);
		builder->bytesInPacket += hdr_size;
		gf_free(hdr);

		if (builder->OnDataReference) {
			builder->OnDataReference(builder->cbk_obj, size, pay_start + txt_done);
		} else {
			builder->OnData(builder->cbk_obj, data + pay_start + txt_done, size, GF_FALSE);
		}
		builder->bytesInPacket += size;
		cur_frag++;

		/*flush packet*/
		if (cur_frag == nb_frag) {
			txt_done = txt_size;
			if (pay_start + txt_done == data_size) {
				builder->rtp_header.Marker = 1;
				builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
				builder->bytesInPacket = 0;
			}
		} else {
			txt_done += size;
			builder->rtp_header.Marker = 0;
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
		}
	}

	txt_done = txt_size;

	/*write all modifiers - OPT: split at modifiers boundaries*/
	while (txt_done<samp_size) {
		u32 size, type;
		type = (txt_done == txt_size) ? 3 : 4;

		if (txt_done + (builder->Path_MTU-7) < samp_size) {
			size = builder->Path_MTU-10;
		} else {
			size = samp_size - txt_done;
		}

		bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
		gf_bs_write_int(bs, is_utf_16, 1);
		gf_bs_write_int(bs, 0, 4);
		gf_bs_write_int(bs, type, 3);
		gf_bs_write_u16(bs, 6 + size);
		gf_bs_write_int(bs, nb_frag, 4);
		gf_bs_write_int(bs, cur_frag, 4);
		gf_bs_write_u24(bs, duration);

		gf_bs_get_content(bs, &hdr, &hdr_size);
		gf_bs_del(bs);
		builder->OnData(builder->cbk_obj, (char *) hdr, hdr_size, GF_FALSE);
		builder->bytesInPacket += hdr_size;
		gf_free(hdr);

		if (builder->OnDataReference) {
			builder->OnDataReference(builder->cbk_obj, size, pay_start + txt_done);
		} else {
			builder->OnData(builder->cbk_obj, data + pay_start + txt_done, size, GF_FALSE);
		}
		builder->bytesInPacket += size;
		cur_frag++;
		if (cur_frag==nb_frag) {
			builder->rtp_header.Marker = 1;
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
		} else {
			builder->rtp_header.Marker = 0;
			builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			builder->bytesInPacket = 0;
		}
		txt_done += size;
	}
	return GF_OK;
}


#if GPAC_ENABLE_3GPP_DIMS_RTP
GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration)
{
	u32 frag_state;
	GF_BitStream *bs;
	u32 offset;
	Bool is_last_du;

	/*the DIMS hinter doesn't perform inter-sample concatenation*/
	if (!data) return GF_OK;

	offset = 0;
	builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
	bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ);
	while (offset < data_size) {
		u32 du_offset = 0;
		u32 hdr_offset = 0;
		u32 orig_size, du_size;

		orig_size = du_size = 2+gf_bs_read_u16(bs);
		/*if dims size is >0xFFFF, use our internal hack for large units*/
		if (du_size==2) {
			orig_size = du_size = 2+gf_bs_read_u32(bs);
			hdr_offset = 4;
		}
		gf_bs_skip_bytes(bs, du_size-2);

		/*prepare M-bit*/
		is_last_du = (offset+du_size==data_size) ? GF_TRUE : GF_FALSE;

		frag_state = 0;
		while (du_size) {
			u32 size_offset = 0;
			u32 size;
			//size = du_size;

			/*does not fit, flush required*/
			if (builder->bytesInPacket && (du_size + 1 + builder->bytesInPacket > builder->Path_MTU)) {
				builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
				builder->bytesInPacket = 0;
			}

			/*fragmentation required*/
			if (du_size + 1 > builder->Path_MTU) {
				size = builder->Path_MTU - 1;
				/*first fragment*/
				if (!frag_state) {
					/*size field is skipped !!*/
					size_offset = 2 + hdr_offset;
					frag_state = 1;

					while (du_size - size_offset <= size) {
						size--;
					}
				}
				/*any middle fragment*/
				else frag_state = 2;

				builder->rtp_header.Marker = 0;
			}
			/*last fragment*/
			else if (frag_state) {
				size = du_size;
				frag_state = 3;
				builder->rtp_header.Marker = is_last_du;
			} else {
				size = du_size;
				builder->rtp_header.Marker = is_last_du;
			}

			if (frag_state && builder->bytesInPacket) {
				builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
				builder->bytesInPacket = 0;
			}

			/*need a new packet*/
			if (!builder->bytesInPacket) {
				char dims_rtp_hdr[1];

				/*the unit is critical, increase counter (coded on 3 bits)*/
				if (! (data[2+hdr_offset] & GF_DIMS_UNIT_P) && (frag_state <= 1) ) {
					builder->last_au_sn++;
					builder->last_au_sn %= 8;
				}
				/*set CTR value*/
				dims_rtp_hdr[0] = builder->last_au_sn;
				/*if M-bit is set in the dims unit header, replicate it*/
				if (data[2+hdr_offset] & (1<<1) ) dims_rtp_hdr[0] |= (1<<6);
				/*add unit fragmentation type*/
				dims_rtp_hdr[0] |= (frag_state<<3);

				builder->rtp_header.SequenceNumber += 1;
				builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
				builder->OnData(builder->cbk_obj, (char *) dims_rtp_hdr, 1, GF_TRUE);
				builder->bytesInPacket = 1;
			}

			/*add payload*/
			if (builder->OnDataReference)
				builder->OnDataReference(builder->cbk_obj, size, offset+du_offset+size_offset);
			else
				builder->OnData(builder->cbk_obj, data+offset+du_offset+size_offset, size, GF_FALSE);

			/*if fragmentation, force packet flush even on last packet since aggregation unit do not
			use the same packet format*/
			if (frag_state) {
				builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
				builder->bytesInPacket = 0;
			} else {
				builder->bytesInPacket += size;
			}
			du_offset += size+size_offset;
			gf_assert(du_size>= size+size_offset);
			du_size -= size+size_offset;
		}
		offset += orig_size;
	}
	if (builder->bytesInPacket) {
		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
		builder->bytesInPacket = 0;
	}
	gf_bs_del(bs);
	return GF_OK;
}
#endif



static void gf_rtp_ac3_flush(GP_RTPPacketizer *builder)
{
	char hdr[2];
	if (!builder->bytesInPacket) return;

	hdr[0] = builder->ac3_ft;
	hdr[1] = builder->last_au_sn;
	builder->OnData(builder->cbk_obj, hdr, 2, GF_TRUE);

	builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
	builder->bytesInPacket = 0;
	builder->last_au_sn = 0;
	builder->ac3_ft = 0;
}

GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	char hdr[2];
	u32 offset, nb_pck;

	/*flush*/
	if (!data) {
		gf_rtp_ac3_flush(builder);
		return GF_OK;
	}

	if (
	    /*AU does not fit*/
	    (builder->bytesInPacket + data_size > builder->Path_MTU)
	    ||
	    /*aggregation is not enabled*/
	    !(builder->flags & GP_RTP_PCK_USE_MULTI)
	    ||
	    /*max ptime is exceeded*/
	    (builder->max_ptime && ( (u32) builder->sl_header.compositionTimeStamp >= builder->rtp_header.TimeStamp + builder->max_ptime) )

	) {
		gf_rtp_ac3_flush(builder);
	}

	/*fits*/
	if (builder->bytesInPacket + data_size < builder->Path_MTU) {
		/*need a new packet*/
		if (!builder->bytesInPacket) {
			builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
			builder->ac3_ft = 0;
			builder->rtp_header.Marker = 1;
			builder->rtp_header.SequenceNumber += 1;
			builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);
			/*2 bytes header*/
			builder->bytesInPacket = 2;
		}

		/*add payload*/
		if (builder->OnDataReference)
			builder->OnDataReference(builder->cbk_obj, data_size, 0);
		else
			builder->OnData(builder->cbk_obj, data, data_size, GF_FALSE);

		builder->bytesInPacket += data_size;
		builder->last_au_sn++;
		return GF_OK;
	}

	/*need fragmentation*/
	gf_assert(!builder->bytesInPacket);
	offset = 0;
	nb_pck = data_size / (builder->Path_MTU-2);
	if (data_size % (builder->Path_MTU-2)) nb_pck++;
	builder->last_au_sn = nb_pck;

	while (offset < data_size) {
		u32 pck_size = MIN(data_size-offset, builder->Path_MTU-2);

		builder->rtp_header.Marker = 0;
		builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
		builder->rtp_header.SequenceNumber += 1;

		if (!offset) {
			builder->ac3_ft = (pck_size > 5*data_size/8) ? 1 : 2;
		} else {
			builder->ac3_ft = 3;
			if (offset + pck_size == data_size)
				builder->rtp_header.Marker = 1;
		}
		builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);

		hdr[0] = builder->ac3_ft;
		hdr[1] = builder->last_au_sn;
		builder->OnData(builder->cbk_obj, hdr, 2, GF_TRUE);

		/*add payload*/
		if (builder->OnDataReference)
			builder->OnDataReference(builder->cbk_obj, pck_size, offset);
		else
			builder->OnData(builder->cbk_obj, data+offset, pck_size, GF_FALSE);

		builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
		offset += pck_size;
		builder->bytesInPacket = 0;
	}

	return GF_OK;
}

GF_Err gp_rtp_builder_do_opus(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize)
{
	/*flush*/
	if (!data) return GF_OK;

	/*fits*/
	if (data_size > builder->Path_MTU) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOpus] Packet size %u larger MTU %u but Opus fragmentation is not yet supported, pacth welcome\n", data_size, builder->Path_MTU ));
		return GF_NOT_SUPPORTED;
	}
	/*send new packet*/
	builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp;
	builder->rtp_header.Marker = 0;
	builder->rtp_header.SequenceNumber += 1;
	builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header);

	/*add payload*/
	if (builder->OnDataReference)
		builder->OnDataReference(builder->cbk_obj, data_size, 0);
	else
		builder->OnData(builder->cbk_obj, data, data_size, GF_FALSE);

	builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header);
	builder->last_au_sn++;
	return GF_OK;
}

#endif /*GPAC_DISABLE_STREAMING*/

