static vod_status_t
manifest_utils_get_multilingual_adaptation_sets(
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t flags,
	label_track_count_array_t* labels,
	adaptation_sets_t* output)
{
	adaptation_set_t* cur_adaptation_set;
	adaptation_set_t* adaptation_sets;
	media_track_t** cur_track_ptr;
	media_track_t* last_track;
	media_track_t* cur_track;
	label_track_count_t* cur_label;
	vod_status_t rc;
	uint32_t media_type;
	size_t adaptation_sets_count;
	size_t index;

	// get the number of adaptation sets
	adaptation_sets_count = labels[MEDIA_TYPE_AUDIO].count + labels[MEDIA_TYPE_SUBTITLE].count;
	output->count[ADAPTATION_TYPE_SUBTITLE] = labels[MEDIA_TYPE_SUBTITLE].count;
	if (media_set->track_count[MEDIA_TYPE_VIDEO] > 0)
	{
		if ((flags & ADAPTATION_SETS_FLAG_FORCE_MUXED) != 0)
		{
			output->count[ADAPTATION_TYPE_MUXED] = 1;
			output->count[ADAPTATION_TYPE_VIDEO] = 0;
			output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count - 1;
		}
		else
		{
			adaptation_sets_count++;
			output->count[ADAPTATION_TYPE_MUXED] = 0;
			output->count[ADAPTATION_TYPE_VIDEO] = 1;
			output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count;
		}
	}
	else
	{
		output->count[ADAPTATION_TYPE_MUXED] = 0;
		output->count[ADAPTATION_TYPE_VIDEO] = 0;
		output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count;
	}

	// allocate the adaptation sets and tracks
	adaptation_sets = vod_alloc(request_context->pool,
		sizeof(adaptation_sets[0]) * adaptation_sets_count +
		sizeof(adaptation_sets[0].first[0]) * media_set->total_track_count);
	if (adaptation_sets == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"manifest_utils_get_multilingual_adaptation_sets: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	cur_track_ptr = (void*)(adaptation_sets + adaptation_sets_count);
	cur_adaptation_set = adaptation_sets;

	// initialize the video adaptation set
	if (media_set->track_count[MEDIA_TYPE_VIDEO] > 0)
	{
		if (output->count[ADAPTATION_TYPE_MUXED] > 0)
		{
			output->first_by_type[ADAPTATION_TYPE_MUXED] = cur_adaptation_set;
			rc = manifest_utils_get_muxed_adaptation_set(
				request_context,
				media_set,
				flags,
				&labels[MEDIA_TYPE_AUDIO].first->label,
				cur_adaptation_set);
			if (rc != VOD_OK)
			{
				return rc;
			}
			cur_adaptation_set++;
			labels[MEDIA_TYPE_AUDIO].first++;		// do not output this label separately
		}
		else
		{
			output->first_by_type[ADAPTATION_TYPE_VIDEO] = cur_adaptation_set;
			cur_adaptation_set->first = cur_track_ptr;
			cur_adaptation_set->count = 0;
			cur_adaptation_set->type = ADAPTATION_TYPE_VIDEO;
			cur_track_ptr += media_set->track_count[MEDIA_TYPE_VIDEO];
			cur_adaptation_set->last = cur_track_ptr;
			cur_adaptation_set++;
		}
	}

	// initialize the audio/subtitle adaptation sets
	for (media_type = MEDIA_TYPE_AUDIO; media_type <= MEDIA_TYPE_SUBTITLE; media_type++)
	{
		output->first_by_type[media_type] = cur_adaptation_set;

		for (cur_label = labels[media_type].first; cur_label < labels[media_type].last; cur_label++)
		{
			cur_adaptation_set->first = cur_track_ptr;
			cur_adaptation_set->count = 0;
			cur_adaptation_set->type = media_type;
			if (media_type != MEDIA_TYPE_AUDIO || (flags & ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK) != 0)
			{
				cur_track_ptr++;
			}
			else
			{
				cur_track_ptr += cur_label->track_count;
			}
			cur_adaptation_set->last = cur_track_ptr;
			cur_adaptation_set++;
		}
	}

	last_track = media_set->filtered_tracks + media_set->total_track_count;
	for (cur_track = media_set->filtered_tracks; cur_track < last_track; cur_track++)
	{
		media_type = cur_track->media_info.media_type;
		switch (media_type)
		{
		case MEDIA_TYPE_AUDIO:
		case MEDIA_TYPE_SUBTITLE:
			if (cur_track->media_info.label.len == 0)
			{
				continue;
			}

			// find the label index
			cur_label = manifest_utils_find_label(
				&cur_track->media_info.label,
				&labels[media_type],
				&index);
			if (cur_label == NULL)
			{
				continue;
			}

			// find the adaptation set
			cur_adaptation_set = output->first_by_type[media_type] + index;

			if ((media_type != MEDIA_TYPE_AUDIO || (flags & ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK) != 0) &&
				cur_adaptation_set->count != 0)
			{
				continue;
			}
			break;

		case MEDIA_TYPE_VIDEO:
			// in forced muxed mode, all video tracks were already added
			if (output->count[ADAPTATION_TYPE_MUXED] > 0)
			{
				continue;
			}

			cur_adaptation_set = adaptation_sets;
			break;

		default:
			continue;
		}

		// add the track to the adaptation set
		cur_adaptation_set->first[cur_adaptation_set->count++] = cur_track;
	}

	output->first = adaptation_sets;
	output->last = adaptation_sets + adaptation_sets_count;
	output->total_count = adaptation_sets_count;

	return VOD_OK;
}
vod_status_t
manifest_utils_get_adaptation_sets(
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t flags,
	adaptation_sets_t* output)
{
	label_track_count_t* last_label;
	label_track_count_t temp_label;
	label_track_count_array_t labels[MEDIA_TYPE_COUNT];
	vod_status_t rc;

	rc = manifest_utils_get_unique_labels(
		request_context,
		media_set,
		MEDIA_TYPE_AUDIO,
		&labels[MEDIA_TYPE_AUDIO]);
	if (rc != VOD_OK)
	{
		return rc;
	}

	if (media_set->track_count[MEDIA_TYPE_SUBTITLE] > 0 &&
		media_set->track_count[MEDIA_TYPE_VIDEO] > 0)		// ignore subtitles if there is no video
	{
		rc = manifest_utils_get_unique_labels(
			request_context,
			media_set,
			MEDIA_TYPE_SUBTITLE,
			&labels[MEDIA_TYPE_SUBTITLE]);
		if (rc != VOD_OK)
		{
			return rc;
		}
	}
	else
	{
		labels[MEDIA_TYPE_SUBTITLE].first = NULL;
		labels[MEDIA_TYPE_SUBTITLE].last = NULL;
		labels[MEDIA_TYPE_SUBTITLE].count = 0;
	}

	if (labels[MEDIA_TYPE_AUDIO].count > 1)
	{
		if ((flags & ADAPTATION_SETS_FLAG_DEFAULT_LANG_LAST) != 0)
		{
			last_label = labels[MEDIA_TYPE_AUDIO].last - 1;
			temp_label = *labels[MEDIA_TYPE_AUDIO].first;
			*labels[MEDIA_TYPE_AUDIO].first = *last_label;
			*last_label = temp_label;
		}

		rc = manifest_utils_get_multilingual_adaptation_sets(
			request_context,
			media_set,
			flags,
			labels,
			output);
	}
	else if ((flags & (ADAPTATION_SETS_FLAG_MUXED | ADAPTATION_SETS_FLAG_FORCE_MUXED)) != 0)
	{
		// cannot generate muxed media set if there are only subtitles
		if (media_set->track_count[MEDIA_TYPE_VIDEO] + media_set->track_count[MEDIA_TYPE_AUDIO] <= 0)
		{
			vod_log_error(VOD_LOG_ERR, request_context->log, 0,
				"manifest_utils_get_adaptation_sets: no audio/video tracks");
			return VOD_BAD_REQUEST;
		}

		output->count[ADAPTATION_TYPE_MUXED] = 1;
		output->count[ADAPTATION_TYPE_AUDIO] = 0;
		output->count[ADAPTATION_TYPE_VIDEO] = 0;
		output->count[ADAPTATION_TYPE_SUBTITLE] = labels[MEDIA_TYPE_SUBTITLE].count;
		output->total_count = 1 + output->count[ADAPTATION_TYPE_SUBTITLE];

		output->first = vod_alloc(request_context->pool, output->total_count * sizeof(output->first[0]));
		if (output->first == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"manifest_utils_get_adaptation_sets: vod_alloc failed");
			return VOD_ALLOC_FAILED;
		}

		output->last = output->first + output->total_count;
		output->first_by_type[ADAPTATION_TYPE_MUXED] = output->first;

		rc = manifest_utils_get_muxed_adaptation_set(
			request_context,
			media_set,
			flags,
			NULL,
			output->first);
		if (rc != VOD_OK)
		{
			return rc;
		}

		if (labels[MEDIA_TYPE_SUBTITLE].count > 0)
		{
			output->first_by_type[ADAPTATION_TYPE_SUBTITLE] = output->first + 1;
			manifest_utils_add_subtitle_adaptation_sets(
				media_set,
				&labels[MEDIA_TYPE_SUBTITLE],
				output->first_by_type[ADAPTATION_TYPE_SUBTITLE],
				output->first->last);
		}
	}
	else
	{
		rc = manifest_utils_get_unmuxed_adaptation_sets(
			request_context,
			media_set,
			&labels[MEDIA_TYPE_SUBTITLE],
			output);
		if (rc != VOD_OK)
		{
			return rc;
		}
	}

	return VOD_OK;
}
vod_status_t
manifest_utils_get_adaptation_sets(
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t flags,
	adaptation_sets_t* output)
{
	track_group_key_t* muxed_audio_key;
	adaptation_set_t* cur_adaptation_set;
	adaptation_set_t* adaptation_sets;
	track_groups_t groups[MEDIA_TYPE_COUNT];
	track_group_t* first_audio_group;
	media_track_t** cur_track_ptr;
	vod_status_t rc;
	uint32_t media_type;
	size_t adaptation_sets_count;

	// update flags
	if (media_set->track_count[MEDIA_TYPE_VIDEO] <= 0)
	{
		// cannot generate muxed media set if there are only subtitles
		if (media_set->track_count[MEDIA_TYPE_AUDIO] <= 0 &&
			(flags & ADAPTATION_SETS_FLAG_MUXED) != 0)
		{
			vod_log_error(VOD_LOG_ERR, request_context->log, 0,
				"manifest_utils_get_adaptation_sets: no audio/video tracks");
			return VOD_BAD_REQUEST;
		}

		flags |= ADAPTATION_SETS_FLAG_IGNORE_SUBTITLES;
	}

	if (manifest_utils_is_multi_audio(media_set))
	{
		flags |= ADAPTATION_SETS_FLAG_MULTI_AUDIO;
		output->multi_audio = TRUE;
	}
	else
	{
		output->multi_audio = FALSE;
	}

	// initialize the track groups
	rc = track_groups_from_media_set(
		request_context,
		media_set,
		flags,
		MEDIA_TYPE_NONE,
		groups);
	if (rc != VOD_OK)
	{
		return rc;
	}

	if (vod_all_flags_set(flags, ADAPTATION_SETS_FLAG_MULTI_AUDIO | ADAPTATION_SETS_FLAG_DEFAULT_LANG_LAST))
	{
		vod_queue_t* first = vod_queue_head(&groups[MEDIA_TYPE_AUDIO].list);
		vod_queue_remove(first);
		vod_queue_insert_tail(&groups[MEDIA_TYPE_AUDIO].list, first);
	}

	// get the number of adaptation sets
	if (groups[MEDIA_TYPE_VIDEO].count > 0 && (flags & ADAPTATION_SETS_FLAG_MUXED) != 0)
	{
		output->count[ADAPTATION_TYPE_MUXED] = 1;
		output->count[ADAPTATION_TYPE_VIDEO] = 0;
		if (groups[MEDIA_TYPE_AUDIO].count > 1)
		{
			output->count[ADAPTATION_TYPE_AUDIO] = groups[MEDIA_TYPE_AUDIO].count;
			if ((flags & ADAPTATION_SETS_FLAG_EXCLUDE_MUXED_AUDIO) != 0)
			{
				output->count[ADAPTATION_TYPE_AUDIO]--;
			}
		}
		else
		{
			output->count[ADAPTATION_TYPE_AUDIO] = 0;
		}
	}
	else
	{
		output->count[ADAPTATION_TYPE_MUXED] = 0;
		output->count[ADAPTATION_TYPE_VIDEO] = groups[MEDIA_TYPE_VIDEO].count;
		output->count[ADAPTATION_TYPE_AUDIO] = groups[MEDIA_TYPE_AUDIO].count;
	}

	output->count[ADAPTATION_TYPE_SUBTITLE] = groups[MEDIA_TYPE_SUBTITLE].count;

	adaptation_sets_count =
		output->count[ADAPTATION_TYPE_VIDEO] +
		output->count[ADAPTATION_TYPE_AUDIO] + 
		output->count[ADAPTATION_TYPE_SUBTITLE] + 
		output->count[ADAPTATION_TYPE_MUXED];

	// allocate the adaptation sets and tracks
	adaptation_sets = vod_alloc(request_context->pool, 
		sizeof(adaptation_sets[0]) * adaptation_sets_count + 
		sizeof(adaptation_sets[0].first[0]) * media_set->total_track_count);
	if (adaptation_sets == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"manifest_utils_get_adaptation_sets: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	cur_track_ptr = (void*)(adaptation_sets + adaptation_sets_count);
	cur_adaptation_set = adaptation_sets;

	if (output->count[ADAPTATION_TYPE_MUXED] > 0)
	{
		// initialize the muxed adaptation set
		first_audio_group = vod_container_of(
			vod_queue_head(&groups[MEDIA_TYPE_AUDIO].list), track_group_t, list_node);

		muxed_audio_key = NULL;
		if (output->count[ADAPTATION_TYPE_AUDIO] > 0)
		{
			flags |= ADAPTATION_SETS_FLAG_AVOID_AUDIO_ONLY;

			if ((flags & ADAPTATION_SETS_FLAG_EXCLUDE_MUXED_AUDIO) != 0)
			{
				muxed_audio_key = &first_audio_group->key;
				vod_queue_remove(&first_audio_group->list_node);	// do not output this label separately
			}
		}

		output->first_by_type[ADAPTATION_TYPE_MUXED] = cur_adaptation_set;
		rc = manifest_utils_get_muxed_adaptation_set(
			request_context,
			media_set,
			flags,
			muxed_audio_key,
			cur_adaptation_set);
		if (rc != VOD_OK)
		{
			return rc;
		}
		cur_adaptation_set++;
	}

	// initialize all other adaptation sets
	for (media_type = MEDIA_TYPE_VIDEO; media_type < MEDIA_TYPE_COUNT; media_type++)
	{
		if (output->count[media_type] <= 0)
		{
			continue;
		}

		output->first_by_type[media_type] = cur_adaptation_set;

		cur_adaptation_set = track_groups_to_adaptation_sets(
			&groups[media_type],
			&cur_track_ptr,
			cur_adaptation_set);
	}

	output->first = adaptation_sets;
	output->last = adaptation_sets + adaptation_sets_count;
	output->total_count = adaptation_sets_count;

	return VOD_OK;
}