/** @internal @This checks and parses a "#EXT-X-BYTERANGE" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_process_byte_range(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; struct uref *item; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); UBASE_RETURN(upipe_m3u_reader_get_item(upipe, flow_def, &item)); char *endptr = NULL; unsigned long long byte_range_len = strtoull(line, &endptr, 10); if (endptr == line || (*endptr != '\0' && *endptr != '@')) { upipe_warn_va(upipe, "invalid byte range %s", line); return UBASE_ERR_INVALID; } upipe_verbose_va(upipe, "byte range length: %"PRIu64, byte_range_len); if (*endptr == '@') { line = endptr + 1; unsigned long long byte_range_off = strtoull(line, &endptr, 10); if (endptr == line || *endptr != '\0') { upipe_warn_va(upipe, "invalid byte range %s", line); return UBASE_ERR_INVALID; } upipe_verbose_va(upipe, "byte range offset: %"PRIu64, byte_range_off); UBASE_RETURN(uref_m3u_playlist_set_byte_range_off( item, byte_range_off)); } return uref_m3u_playlist_set_byte_range_len(item, byte_range_len); }
/** @internal @This sets the input flow definition. * * @param upipe description structure of the pipe * @param flow_def flow definition packet * @return an error code */ static int upipe_s337_encaps_set_flow_def(struct upipe *upipe, struct uref *flow_def) { if (flow_def == NULL) return UBASE_ERR_INVALID; const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)) uint64_t rate; UBASE_RETURN(uref_sound_flow_get_rate(flow_def, &rate)) if (ubase_ncmp(def, EXPECTED_FLOW_DEF)) return UBASE_ERR_INVALID; struct uref *flow_def_dup = uref_dup(flow_def); if (flow_def_dup == NULL) return UBASE_ERR_ALLOC; uref_flow_set_def(flow_def_dup, "sound.s32.s337.a52."); uref_sound_flow_set_channels(flow_def_dup, 2); uref_sound_flow_set_sample_size(flow_def_dup, 2*4); if (!ubase_check(uref_sound_flow_add_plane(flow_def_dup, "lr")) || !ubase_check(uref_sound_flow_set_rate(flow_def_dup, rate))) { uref_free(flow_def_dup); return UBASE_ERR_ALLOC; } upipe_s337_encaps_require_ubuf_mgr(upipe, flow_def_dup); return UBASE_ERR_NONE; }
/** @internal @This checks and parses a "#EXT-X-STREAM-INF" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_ext_x_stream_inf(struct upipe *upipe, struct uref *flow_def, const char *line) { if (!ubase_check(uref_flow_match_def(flow_def, M3U_FLOW_DEF)) && !ubase_check(uref_flow_match_def(flow_def, MASTER_FLOW_DEF))) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, MASTER_FLOW_DEF)); struct uref *item; UBASE_RETURN(upipe_m3u_reader_get_item(upipe, flow_def, &item)); const char *iterator = line; struct ustring name, value; while (ubase_check(attribute_iterate(&iterator, &name, &value)) && iterator != NULL) { char value_str[value.len + 1]; int err = ustring_cpy(value, value_str, sizeof (value_str)); if (unlikely(!ubase_check(err))) { upipe_err_va(upipe, "fail to copy ustring %.*s", (int)value.len, value.at); continue; } if (!ustring_cmp_str(name, "BANDWIDTH")) { char *endptr; uint64_t bandwidth = strtoull(value_str, &endptr, 10); if (endptr == value_str) return UBASE_ERR_INVALID; err = uref_m3u_master_set_bandwidth(item, bandwidth); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set bandwidth to %s", value_str); } else if (!ustring_cmp_str(name, "CODECS")) { err = uref_m3u_master_set_codecs(item, value_str); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set codecs to %s", value_str); } else if (!ustring_cmp_str(name, "AUDIO")) { err = uref_m3u_master_set_audio(item, value_str); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set audio to %s", value_str); } else if (!ustring_cmp_str(name, "RESOLUTION")) { err = uref_m3u_master_set_resolution(item, value_str); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set resolution to %s", value_str); } else { upipe_warn_va(upipe, "ignoring attribute %.*s (%.*s)", (int)name.len, name.at, (int)value.len, value.at); } } return UBASE_ERR_NONE; }
/** @internal @This checks a "#EXT-X-ENDLIST" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_ext_x_endlist(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); upipe_verbose(upipe, "endlist tag"); return uref_m3u_playlist_flow_set_endlist(flow_def); }
/** @internal @This handles data. * * @param upipe description structure of the pipe * @param uref uref structure * @param upump_p reference to pump that generated the buffer * @return false if the input must be blocked */ static bool upipe_tblk_handle(struct upipe *upipe, struct uref *uref, struct upump **upump_p) { struct upipe_tblk *upipe_tblk = upipe_tblk_from_upipe(upipe); const char *def; if (unlikely(ubase_check(uref_flow_get_def(uref, &def)))) { if (!ubase_ncmp(def, "pic.")) { upipe_tblk->input_alloc = UBUF_ALLOC_PICTURE; uref_pic_flow_clear_format(uref); uref_flow_set_def(uref, "block."); } else if (!ubase_ncmp(def, "sound.")) { upipe_tblk->input_alloc = UBUF_ALLOC_SOUND; uref_sound_flow_clear_format(uref); uref_flow_set_def(uref, "block."); } else upipe_tblk->input_alloc = UBUF_ALLOC_BLOCK; upipe_tblk_store_flow_def(upipe, NULL); upipe_tblk_require_ubuf_mgr(upipe, uref); return true; } if (upipe_tblk->flow_def == NULL) return false; assert(upipe_tblk->ubuf_mgr != NULL); switch (upipe_tblk->input_alloc) { case UBUF_ALLOC_PICTURE: upipe_tblk_handle_pic(upipe, uref, upump_p); break; case UBUF_ALLOC_SOUND: upipe_tblk_handle_sound(upipe, uref, upump_p); break; default: upipe_tblk_output(upipe, uref, upump_p); break; } return true; }
/** @internal @This provides a flow format suggestion. * * @param upipe description structure of the pipe * @param request description structure of the request * @return an error code */ static int upipe_filter_ebur128_provide_flow_format(struct upipe *upipe, struct urequest *request) { const char *def; UBASE_RETURN(uref_flow_get_def(request->uref, &def)) struct uref *flow = uref_dup(request->uref); UBASE_ALLOC_RETURN(flow); if (!ubase_ncmp(def, "sound.u8.")) UBASE_FATAL(upipe, uref_flow_set_def(flow, "sound.s16.")); return urequest_provide_flow_format(request, flow); }
/** @internal @This provides a flow format suggestion. * * @param upipe description structure of the pipe * @param request description structure of the request * @return an error code */ static int upipe_nacl_audio_provide_flow_format(struct upipe *upipe, struct urequest *request) { struct uref *flow_format = uref_dup(request->uref); UBASE_ALLOC_RETURN(flow_format); uref_sound_flow_clear_format(flow_format); uref_flow_set_def(flow_format, EXPECTED_FLOW_DEF); uref_sound_flow_set_channels(flow_format, 2); uref_sound_flow_set_sample_size(flow_format, 4); uref_sound_flow_set_planes(flow_format, 0); uref_sound_flow_add_plane(flow_format, "lr"); uref_sound_flow_set_rate(flow_format, SAMPLE_RATE); return urequest_provide_flow_format(request, flow_format); }
/** @internal @This sets the input flow definition. * * @param upipe description structure of the pipe * @param flow_def flow definition packet * @return an error code */ static int upipe_ts_check_set_flow_def(struct upipe *upipe, struct uref *flow_def) { if (flow_def == NULL) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_match_def(flow_def, EXPECTED_FLOW_DEF)) struct uref *flow_def_dup; if (unlikely((flow_def_dup = uref_dup(flow_def)) == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return UBASE_ERR_ALLOC; } struct upipe_ts_check *upipe_ts_check = upipe_ts_check_from_upipe(upipe); UBASE_RETURN(uref_block_flow_set_size(flow_def_dup, upipe_ts_check->output_size)) UBASE_RETURN(uref_flow_set_def(flow_def_dup, OUTPUT_FLOW_DEF)) upipe_ts_check_store_flow_def(upipe, flow_def_dup); return UBASE_ERR_NONE; }
static int upipe_rtp_opus_set_flow_def(struct upipe *upipe, struct uref *flow_def) { if (flow_def == NULL) return UBASE_ERR_INVALID; //UBASE_RETURN(uref_flow_match_def(flow_def, "block.opus.sound.")) struct uref *flow_def_dup = uref_dup(flow_def); if (unlikely(flow_def_dup == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return UBASE_ERR_ALLOC; } uref_flow_set_def(flow_def_dup, "block.opus.sound."); upipe_rtp_opus_store_flow_def(upipe, flow_def_dup); return UBASE_ERR_NONE; }
/** @internal @This checks a "#EXTM3U" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_process_m3u(struct upipe *upipe, struct uref *flow_def, const char *line) { while (isspace(*line)) line++; if (strlen(line)) { upipe_err(upipe, "invalid EXTM3U tag"); return UBASE_ERR_INVALID; } upipe_verbose(upipe, "found EXTM3U tag"); const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, EXPECTED_FLOW_DEF)) return UBASE_ERR_INVALID; return uref_flow_set_def(flow_def, M3U_FLOW_DEF); }
/** @internal @This checks and parses a "#EXT-X-MEDIA-SEQUENCE" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_ext_x_media_sequence(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); char *endptr; uint64_t media_sequence = strtoull(line, &endptr, 10); if (endptr == line || strlen(endptr)) { upipe_warn_va(upipe, "invalid media sequence %s", line); return UBASE_ERR_INVALID; } upipe_dbg_va(upipe, "media sequence %"PRIu64, media_sequence); return uref_m3u_playlist_flow_set_media_sequence(flow_def, media_sequence); }
/** @internal @This checks and parses a "#EXT-X-TARGETDURATION" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_process_target_duration(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); const char *endptr; uint64_t duration; UBASE_RETURN(duration_to_uclock(line, &endptr, &duration)); if (line == endptr || strlen(endptr)) { upipe_warn_va(upipe, "invalid target duration %s", line); return UBASE_ERR_INVALID; } upipe_dbg_va(upipe, "target duration: %"PRIu64, duration); return uref_m3u_playlist_flow_set_target_duration(flow_def, duration); }
/** @internal @This handles input. * * @param upipe description structure of the pipe * @param uref uref structure * @param upump pump that generated the buffer */ static void upipe_skip_input(struct upipe *upipe, struct uref *uref, struct upump *upump) { struct upipe_skip *upipe_skip = upipe_skip_from_upipe(upipe); const char *def; if (unlikely(uref_flow_get_def(uref, &def))) { if (unlikely(ubase_ncmp(def, EXPECTED_FLOW))) { upipe_throw_flow_def_error(upipe, uref); uref_free(uref); return; } upipe_dbg_va(upipe, "flow definition %s", def); if (unlikely(!uref_flow_set_def(uref, "block."))) upipe_throw_aerror(upipe); upipe_skip_store_flow_def(upipe, uref); return; } if (unlikely(uref_flow_get_end(uref))) { uref_free(uref); upipe_throw_need_input(upipe); return; } if (unlikely(upipe_skip->flow_def == NULL)) { upipe_throw_flow_def_error(upipe, uref); uref_free(uref); return; } if (unlikely(uref->ubuf == NULL)) { uref_free(uref); return; } upipe_skip_input_block(upipe, uref, upump); }
/** @internal @This checks and parses a "#EXTINF" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_process_extinf(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; struct uref *item; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); UBASE_RETURN(upipe_m3u_reader_get_item(upipe, flow_def, &item)); const char *endptr; uint64_t duration; UBASE_RETURN(duration_to_uclock(line, &endptr, &duration)); if (line == endptr || *endptr != ',') { upipe_err_va(upipe, "invalid segment duration `%s'", line); return UBASE_ERR_INVALID; } upipe_verbose_va(upipe, "segment duration: %"PRIu64, duration); return uref_m3u_playlist_set_seq_duration(item, duration); }
/** @internal @This checks and parses a "#EXT-X-PLAYLIST-TYPE" tag. * * @param upipe description structure of the pipe * @param flow_def the current flow definition * @param line the trailing characters of the line * @return an error code */ static int upipe_m3u_reader_process_playlist_type(struct upipe *upipe, struct uref *flow_def, const char *line) { const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)); if (strcmp(def, M3U_FLOW_DEF) && strcmp(def, PLAYLIST_FLOW_DEF)) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); static const char *types[] = { "VOD", "EVENT" }; const char *type = NULL; for (unsigned i = 0; !type && i < UBASE_ARRAY_SIZE(types); i++) if (!strcmp(line, types[i])) type = types[i]; if (unlikely(type == NULL)) { upipe_warn_va(upipe, "invalid playlist type `%s'", line); return UBASE_ERR_INVALID; } upipe_dbg_va(upipe, "playlist type %s", type); return uref_m3u_playlist_flow_set_type(flow_def, type); }
/** @internal @This provides a flow format suggestion. * * @param upipe description structure of the pipe * @param request description structure of the request * @return an error code */ static int upipe_filter_ebur128_provide_flow_format(struct upipe *upipe, struct urequest *request) { struct uref *flow = uref_dup(request->uref); UBASE_ALLOC_RETURN(flow); uint8_t planes; if (ubase_check(uref_sound_flow_get_planes(request->uref, &planes)) && planes != 1) { /* compute sample size */ uint8_t sample_size = 0; uref_sound_flow_get_sample_size(request->uref, &sample_size); sample_size *= planes; /* construct packed channel name from planar names */ char packed_channel[planes+1]; uint8_t plane; for (plane=0; plane < planes; plane++) { const char *planar_channel = ""; uref_sound_flow_get_channel(request->uref, &planar_channel, plane); packed_channel[plane] = *planar_channel; } packed_channel[planes] = '\0'; /* set attributes */ uref_sound_flow_clear_format(flow); UBASE_FATAL(upipe, uref_sound_flow_set_channels(flow, planes)); UBASE_FATAL(upipe, uref_sound_flow_set_sample_size(flow, sample_size)); UBASE_FATAL(upipe, uref_sound_flow_set_channel(flow, packed_channel, 0)); UBASE_FATAL(upipe, uref_sound_flow_set_planes(flow, 1)); } UBASE_FATAL(upipe, uref_flow_set_def(flow, "sound.s16.")); return urequest_provide_flow_format(request, flow); }
/** @internal @This handles data. * * @param upipe description structure of the pipe * @param uref uref structure * @param upump_p reference to pump that generated the buffer * @return false if the input must be blocked */ static bool upipe_speexdsp_handle(struct upipe *upipe, struct uref *uref, struct upump **upump_p) { struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); struct urational drift_rate; if (!ubase_check(uref_clock_get_rate(uref, &drift_rate))) drift_rate = (struct urational){ 1, 1 }; /* reinitialize resampler when drift rate changes */ if (urational_cmp(&drift_rate, &upipe_speexdsp->drift_rate)) { upipe_speexdsp->drift_rate = drift_rate; spx_uint32_t ratio_num = drift_rate.den; spx_uint32_t ratio_den = drift_rate.num; spx_uint32_t in_rate = upipe_speexdsp->rate * ratio_num / ratio_den; spx_uint32_t out_rate = upipe_speexdsp->rate; int err = speex_resampler_set_rate_frac(upipe_speexdsp->ctx, ratio_num, ratio_den, in_rate, out_rate); if (err) { upipe_err_va(upipe, "Couldn't resample from %u to %u: %s", in_rate, out_rate, speex_resampler_strerror(err)); } else { upipe_dbg_va(upipe, "Resampling from %u to %u", in_rate, out_rate); } } size_t size; if (!ubase_check(uref_sound_size(uref, &size, NULL /* sample_size */))) { uref_free(uref); return true; } struct ubuf *ubuf = ubuf_sound_alloc(upipe_speexdsp->ubuf_mgr, size + 10); if (!ubuf) return false; const void *in; uref_sound_read_void(uref, 0, -1, &in, 1); void *out; ubuf_sound_write_void(ubuf, 0, -1, &out, 1); spx_uint32_t in_len = size; /* input size */ spx_uint32_t out_len = size + 10; /* available output size */ int err; if (upipe_speexdsp->f32) err = speex_resampler_process_interleaved_float(upipe_speexdsp->ctx, in, &in_len, out, &out_len); else err = speex_resampler_process_interleaved_int(upipe_speexdsp->ctx, in, &in_len, out, &out_len); if (err) { upipe_err_va(upipe, "Could not resample: %s", speex_resampler_strerror(err)); } uref_sound_unmap(uref, 0, -1, 1); ubuf_sound_unmap(ubuf, 0, -1, 1); if (err) { ubuf_free(ubuf); } else { ubuf_sound_resize(ubuf, 0, out_len); uref_attach_ubuf(uref, ubuf); } upipe_speexdsp_output(upipe, uref, upump_p); return true; } /** @internal @This receives incoming uref. * * @param upipe description structure of the pipe * @param uref uref structure describing the picture * @param upump_p reference to pump that generated the buffer */ static void upipe_speexdsp_input(struct upipe *upipe, struct uref *uref, struct upump **upump_p) { if (!upipe_speexdsp_check_input(upipe)) { upipe_speexdsp_hold_input(upipe, uref); upipe_speexdsp_block_input(upipe, upump_p); } else if (!upipe_speexdsp_handle(upipe, uref, upump_p)) { upipe_speexdsp_hold_input(upipe, uref); upipe_speexdsp_block_input(upipe, upump_p); /* Increment upipe refcount to avoid disappearing before all packets * have been sent. */ upipe_use(upipe); } } /** @internal @This receives a provided ubuf manager. * * @param upipe description structure of the pipe * @param flow_format amended flow format * @return an error code */ static int upipe_speexdsp_check(struct upipe *upipe, struct uref *flow_format) { struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); if (flow_format != NULL) upipe_speexdsp_store_flow_def(upipe, flow_format); if (upipe_speexdsp->flow_def == NULL) return UBASE_ERR_NONE; bool was_buffered = !upipe_speexdsp_check_input(upipe); upipe_speexdsp_output_input(upipe); upipe_speexdsp_unblock_input(upipe); if (was_buffered && upipe_speexdsp_check_input(upipe)) { /* All packets have been output, release again the pipe that has been * used in @ref upipe_speexdsp_input. */ upipe_release(upipe); } return UBASE_ERR_NONE; } /** @internal @This sets the input flow definition. * * @param upipe description structure of the pipe * @param flow_def flow definition packet * @return an error code */ static int upipe_speexdsp_set_flow_def(struct upipe *upipe, struct uref *flow_def) { struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); if (flow_def == NULL) return UBASE_ERR_INVALID; const char *def; UBASE_RETURN(uref_flow_get_def(flow_def, &def)) if (unlikely(ubase_ncmp(def, "sound.f32.") && ubase_ncmp(def, "sound.s16."))) return UBASE_ERR_INVALID; uint8_t in_planes; if (unlikely(!ubase_check(uref_sound_flow_get_planes(flow_def, &in_planes)))) return UBASE_ERR_INVALID; if (in_planes != 1) { upipe_err(upipe, "only interleaved audio is supported"); return UBASE_ERR_INVALID; } if (!ubase_check(uref_sound_flow_get_rate(flow_def, &upipe_speexdsp->rate))) { upipe_err(upipe, "no sound rate defined"); uref_dump(flow_def, upipe->uprobe); return UBASE_ERR_INVALID; } uint8_t channels; if (unlikely(!ubase_check(uref_sound_flow_get_channels(flow_def, &channels)))) return UBASE_ERR_INVALID; flow_def = uref_dup(flow_def); if (unlikely(flow_def == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return UBASE_ERR_ALLOC; } upipe_speexdsp_require_ubuf_mgr(upipe, flow_def); if (upipe_speexdsp->ctx) speex_resampler_destroy(upipe_speexdsp->ctx); upipe_speexdsp->f32 = !ubase_ncmp(def, "sound.f32."); int err; upipe_speexdsp->ctx = speex_resampler_init(channels, upipe_speexdsp->rate, upipe_speexdsp->rate, upipe_speexdsp->quality, &err); if (!upipe_speexdsp->ctx) { upipe_err_va(upipe, "Could not create resampler: %s", speex_resampler_strerror(err)); return UBASE_ERR_INVALID; } return UBASE_ERR_NONE; } /** @internal @This provides a flow format suggestion. * * @param upipe description structure of the pipe * @param request description structure of the request * @return an error code */ static int upipe_speexdsp_provide_flow_format(struct upipe *upipe, struct urequest *request) { const char *def; UBASE_RETURN(uref_flow_get_def(request->uref, &def)) uint8_t channels; UBASE_RETURN(uref_sound_flow_get_channels(request->uref, &channels)) uint8_t planes; UBASE_RETURN(uref_sound_flow_get_planes(request->uref, &planes)) uint8_t sample_size; UBASE_RETURN(uref_sound_flow_get_sample_size(request->uref, &sample_size)) struct uref *flow = uref_dup(request->uref); UBASE_ALLOC_RETURN(flow); uref_sound_flow_clear_format(flow); uref_sound_flow_set_planes(flow, 0); uref_sound_flow_set_channels(flow, channels); uref_sound_flow_add_plane(flow, "all"); if (ubase_ncmp(def, "sound.s16.")) { uref_flow_set_def(flow, "sound.f32."); /* prefer f32 over s16 */ uref_sound_flow_set_sample_size(flow, 4 * channels); } else { uref_flow_set_def(flow, def); uref_sound_flow_set_sample_size(flow, (planes > 1) ? sample_size : sample_size / channels); } return urequest_provide_flow_format(request, flow); } /** @internal @This processes control commands on a speexdsp pipe. * * @param upipe description structure of the pipe * @param command type of command to process * @param args arguments of the command * @return an error code */ static int upipe_speexdsp_control(struct upipe *upipe, int command, va_list args) { struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); switch (command) { /* generic commands */ case UPIPE_REGISTER_REQUEST: { struct urequest *request = va_arg(args, struct urequest *); if (request->type == UREQUEST_FLOW_FORMAT) return upipe_speexdsp_provide_flow_format(upipe, request); if (request->type == UREQUEST_UBUF_MGR) return upipe_throw_provide_request(upipe, request); return upipe_speexdsp_alloc_output_proxy(upipe, request); } case UPIPE_UNREGISTER_REQUEST: { struct urequest *request = va_arg(args, struct urequest *); if (request->type == UREQUEST_FLOW_FORMAT || request->type == UREQUEST_UBUF_MGR) return UBASE_ERR_NONE; return upipe_speexdsp_free_output_proxy(upipe, request); } case UPIPE_GET_OUTPUT: { struct upipe **p = va_arg(args, struct upipe **); return upipe_speexdsp_get_output(upipe, p); } case UPIPE_SET_OUTPUT: { struct upipe *output = va_arg(args, struct upipe *); return upipe_speexdsp_set_output(upipe, output); } case UPIPE_GET_FLOW_DEF: { struct uref **p = va_arg(args, struct uref **); return upipe_speexdsp_get_flow_def(upipe, p); } case UPIPE_SET_FLOW_DEF: { struct uref *flow = va_arg(args, struct uref *); return upipe_speexdsp_set_flow_def(upipe, flow); } case UPIPE_SET_OPTION: { const char *option = va_arg(args, const char *); const char *value = va_arg(args, const char *); if (strcmp(option, "quality")) return UBASE_ERR_INVALID; if (upipe_speexdsp->ctx) return UBASE_ERR_BUSY; int quality = atoi(value); if (quality > SPEEX_RESAMPLER_QUALITY_MAX) { quality = SPEEX_RESAMPLER_QUALITY_MAX; upipe_err_va(upipe, "Clamping quality to %d", SPEEX_RESAMPLER_QUALITY_MAX); } else if (quality < SPEEX_RESAMPLER_QUALITY_MIN) { quality = SPEEX_RESAMPLER_QUALITY_MIN; upipe_err_va(upipe, "Clamping quality to %d", SPEEX_RESAMPLER_QUALITY_MIN); } upipe_speexdsp->quality = quality; return UBASE_ERR_NONE; } default: return UBASE_ERR_UNHANDLED; } } /** @internal @This allocates a speexdsp pipe. * * @param mgr common management structure * @param uprobe structure used to raise events * @param signature signature of the pipe allocator * @param args optional arguments * @return pointer to upipe or NULL in case of allocation error */ static struct upipe *upipe_speexdsp_alloc(struct upipe_mgr *mgr, struct uprobe *uprobe, uint32_t signature, va_list args) { struct upipe *upipe = upipe_speexdsp_alloc_void(mgr, uprobe, signature, args); if (unlikely(upipe == NULL)) return NULL; struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); upipe_speexdsp->ctx = NULL; upipe_speexdsp->drift_rate = (struct urational){ 0, 0 }; upipe_speexdsp->quality = SPEEX_RESAMPLER_QUALITY_MAX; upipe_speexdsp_init_urefcount(upipe); upipe_speexdsp_init_ubuf_mgr(upipe); upipe_speexdsp_init_output(upipe); upipe_speexdsp_init_flow_def(upipe); upipe_speexdsp_init_input(upipe); upipe_throw_ready(upipe); return upipe; } /** @This frees a upipe. * * @param upipe description structure of the pipe */ static void upipe_speexdsp_free(struct upipe *upipe) { struct upipe_speexdsp *upipe_speexdsp = upipe_speexdsp_from_upipe(upipe); if (likely(upipe_speexdsp->ctx)) speex_resampler_destroy(upipe_speexdsp->ctx); upipe_throw_dead(upipe); upipe_speexdsp_clean_input(upipe); upipe_speexdsp_clean_output(upipe); upipe_speexdsp_clean_flow_def(upipe); upipe_speexdsp_clean_ubuf_mgr(upipe); upipe_speexdsp_clean_urefcount(upipe); upipe_speexdsp_free_void(upipe); } /** module manager static descriptor */ static struct upipe_mgr upipe_speexdsp_mgr = { .refcount = NULL, .signature = UPIPE_SPEEXDSP_SIGNATURE, .upipe_alloc = upipe_speexdsp_alloc, .upipe_input = upipe_speexdsp_input, .upipe_control = upipe_speexdsp_control, .upipe_mgr_control = NULL }; /** @This returns the management structure for speexdsp pipes * * @return pointer to manager */ struct upipe_mgr *upipe_speexdsp_mgr_alloc(void) { return &upipe_speexdsp_mgr; }
/** avcdec callback */ static int avcdec_catch(struct uprobe *uprobe, struct upipe *upipe, int event, va_list args) { if (event != UPROBE_NEED_OUTPUT) return uprobe_throw_next(uprobe, upipe, event, args); struct uref *flow_def = va_arg(args, struct uref *); uint64_t hsize, vsize, wanted_hsize; struct urational sar; bool progressive; if (unlikely(!ubase_check(uref_pic_flow_get_hsize(flow_def, &hsize)) || !ubase_check(uref_pic_flow_get_vsize(flow_def, &vsize)) || !ubase_check(uref_pic_flow_get_sar(flow_def, &sar)))) { upipe_err_va(upipe, "incompatible flow def"); upipe_release(upipe_source); return UBASE_ERR_UNHANDLED; } wanted_hsize = (hsize * sar.num / sar.den / 2) * 2; progressive = ubase_check(uref_pic_get_progressive(flow_def)); struct uref *flow_def2 = uref_dup(flow_def); upipe_use(upipe); if (!progressive) { uref_pic_set_progressive(flow_def2); struct upipe *deint = upipe_void_alloc_output(upipe, upipe_filter_blend_mgr, uprobe_pfx_alloc(uprobe_use(logger), loglevel, "deint")); assert(deint != NULL); upipe_release(upipe); upipe = deint; } if (wanted_hsize != hsize) { uref_pic_flow_set_hsize(flow_def2, wanted_hsize); struct upipe *sws = upipe_flow_alloc_output(upipe, upipe_sws_mgr, uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "sws"), flow_def2); assert(sws != NULL); upipe_release(upipe); upipe = sws; } uref_pic_flow_clear_format(flow_def2); uref_flow_set_def(flow_def2, "block.mjpeg.pic."); struct upipe *jpegenc = upipe_flow_alloc_output(upipe, upipe_avcenc_mgr, uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "jpeg"), flow_def2); assert(jpegenc != NULL); upipe_release(upipe); upipe_set_option(jpegenc, "qmax", "2"); upipe = jpegenc; struct upipe *urefprobe = upipe_void_alloc_output(upipe, upipe_probe_uref_mgr, uprobe_pfx_alloc_va(uprobe_use(&uprobe_uref), loglevel, "urefprobe")); assert(urefprobe != NULL); upipe_release(upipe); upipe = urefprobe; struct upipe *fsink = upipe_void_alloc_output(upipe, upipe_fsink_mgr, uprobe_pfx_alloc_va(uprobe_use(logger), ((loglevel > UPROBE_LOG_DEBUG) ? UPROBE_LOG_WARNING : loglevel), "jpegsink")); assert(fsink != NULL); upipe_release(upipe); upipe_fsink_set_path(fsink, dstpath, UPIPE_FSINK_OVERWRITE); upipe = fsink; uref_free(flow_def2); upipe_release(upipe); return UBASE_ERR_NONE; }
/** @internal @This handles data. * * @param upipe description structure of the pipe * @param uref uref structure * @param upump_p reference to pump that generated the buffer */ static inline void upipe_rtpd_input(struct upipe *upipe, struct uref *uref, struct upump **upump_p) { struct upipe_rtpd *upipe_rtpd = upipe_rtpd_from_upipe(upipe); uint8_t rtp_buffer[RTP_HEADER_SIZE]; const uint8_t *rtp_header = uref_block_peek(uref, 0, RTP_HEADER_SIZE, rtp_buffer); if (unlikely(rtp_header == NULL)) { upipe_warn(upipe, "invalid buffer received"); uref_free(uref); return; } bool valid = rtp_check_hdr(rtp_header); bool extension = rtp_check_extension(rtp_header); uint8_t cc = rtp_get_cc(rtp_header); uint8_t type = rtp_get_type(rtp_header); uint16_t seqnum = rtp_get_seqnum(rtp_header); ptrdiff_t extension_offset = rtp_extension((uint8_t *)rtp_header) - rtp_header; uref_block_peek_unmap(uref, 0, rtp_buffer, rtp_header); if (unlikely(!valid)) { upipe_warn(upipe, "invalid RTP header"); uref_free(uref); return; } size_t offset = RTP_HEADER_SIZE + 4 * cc; if (extension) { rtp_header = uref_block_peek(uref, extension_offset, RTP_EXTENSION_SIZE, rtp_buffer); if (unlikely(rtp_header == NULL)) { upipe_warn(upipe, "invalid buffer received"); uref_free(uref); return; } offset += 4 * (1 + rtpx_get_length(rtp_header)); uref_block_peek_unmap(uref, extension_offset, rtp_buffer, rtp_header); } if (unlikely(upipe_rtpd->expected_seqnum != -1 && seqnum != upipe_rtpd->expected_seqnum)) { upipe_warn_va(upipe, "potentially lost %d RTP packets", (seqnum + UINT16_MAX + 1 - upipe_rtpd->expected_seqnum) & UINT16_MAX); uref_flow_set_discontinuity(uref); } upipe_rtpd->expected_seqnum = seqnum + 1; upipe_rtpd->expected_seqnum &= UINT16_MAX; if (unlikely(type != upipe_rtpd->type)) { assert(upipe_rtpd->flow_def_input != NULL); struct uref *flow_def = uref_dup(upipe_rtpd->flow_def_input); if (unlikely(flow_def == NULL)) { uref_free(uref); upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return; } switch (type) { case RTP_TYPE_TS: uref_flow_set_def(flow_def, "block.mpegtsaligned."); break; default: break; } upipe_rtpd->type = type; upipe_rtpd_store_flow_def(upipe, flow_def); } uref_block_resize(uref, offset, -1); upipe_rtpd_output(upipe, uref, upump_p); }
/** @internal @This parses a new s337 header. * * @param upipe description structure of the pipe */ static void upipe_s337d_parse_preamble(struct upipe *upipe) { struct upipe_s337d *upipe_s337d = upipe_s337d_from_upipe(upipe); uint8_t preamble[S337_PREAMBLE_SIZE]; if (!ubase_check(uref_block_extract(upipe_s337d->next_uref, 0, S337_PREAMBLE_SIZE, preamble))) return; /* not enough data */ uint8_t data_type = s337_get_data_type(preamble); uint8_t data_mode = s337_get_data_mode(preamble); bool error = s337_get_error(preamble); uint8_t data_stream = s337_get_data_stream(preamble); uint8_t data_type_dep = s337_get_data_type_dep(preamble); upipe_s337d->next_frame_size = s337_get_length(preamble) + 7; upipe_s337d->next_frame_size /= 8; if (data_type != S337_TYPE_A52 && data_type != S337_TYPE_A52E) { upipe_s337d->next_frame_discard = true; return; } if (data_mode != S337_MODE_16) { upipe_err_va(upipe, "unsupported data mode (%"PRIu8")", data_mode); upipe_s337d->next_frame_discard = true; return; } upipe_s337d->next_frame_discard = false; if (error) upipe_warn(upipe, "error flag set"); if (upipe_s337d->data_stream != data_stream) { upipe_dbg_va(upipe, "now following stream %"PRIu8, data_stream); upipe_s337d->data_stream = data_stream; } if (upipe_s337d->data_type == data_type) return; upipe_s337d->data_type = data_type; if (data_type_dep & S337_TYPE_A52_REP_RATE_FLAG) upipe_warn(upipe, "repetition rate flag set"); if (data_type_dep & S337_TYPE_A52_NOT_FULL_SVC) upipe_warn(upipe, "not full service flag set"); struct uref *flow_def = upipe_s337d_alloc_flow_def_attr(upipe); if (unlikely(flow_def == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return; } if (data_type == S337_TYPE_A52) UBASE_FATAL(upipe, uref_flow_set_def(flow_def, "block.ac3.sound.")) else UBASE_FATAL(upipe, uref_flow_set_def(flow_def, "block.eac3.sound.")) flow_def = upipe_s337d_store_flow_def_attr(upipe, flow_def); if (unlikely(!flow_def)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return; } upipe_s337d_store_flow_def(upipe, flow_def); }
static int upipe_m3u_reader_key(struct upipe *upipe, struct uref *flow_def, const char *line) { struct upipe_m3u_reader *upipe_m3u_reader = upipe_m3u_reader_from_upipe(upipe); if (unlikely(!ubase_check(uref_flow_match_def(flow_def, M3U_FLOW_DEF))) && unlikely(!ubase_check(uref_flow_match_def(flow_def, PLAYLIST_FLOW_DEF)))) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, PLAYLIST_FLOW_DEF)); struct uref *key = uref_sibling_alloc_control(flow_def); if (unlikely(key == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return UBASE_ERR_ALLOC; } if (upipe_m3u_reader->key) uref_free(upipe_m3u_reader->key); upipe_m3u_reader->key = key; const char *iterator = line; struct ustring name, value; while (ubase_check(attribute_iterate(&iterator, &name, &value)) && iterator != NULL) { char value_str[value.len + 1]; int err = ustring_cpy(value, value_str, sizeof (value_str)); if (unlikely(!ubase_check(err))) { upipe_err_va(upipe, "fail to copy ustring %.*s", (int)value.len, value.at); continue; } if (!ustring_cmp_str(name, "METHOD")) { err = uref_m3u_playlist_key_set_method(key, value_str); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set key method to %s", value_str); } else if (!ustring_cmp_str(name, "URI")) { err = uref_m3u_playlist_key_set_uri(key, value_str); if (unlikely(!ubase_check(err))) upipe_err_va(upipe, "fail to set uri to %s", value_str); } else if (!ustring_cmp_str(name, "IV")) { size_t len = strlen(value_str); if (unlikely(len > 32)) { upipe_warn_va(upipe, "invalid initialization vector %s", value_str); continue; } for (unsigned i = 0; i < len; i += 2) { if (unlikely(!isxdigit(value_str[i])) || unlikely(!isxdigit(value_str[i + 1]))) { upipe_warn_va(upipe, "invalid initialization vector %s", value_str); continue; } //FIXME //iv[] = value_str[i] } } else { upipe_warn_va(upipe, "ignoring attribute %.*s (%.*s)", (int)name.len, name.at, (int)value.len, value.at); } } return UBASE_ERR_NONE; }
/** @internal @This parses A/52 Annex E header. * * @param upipe description structure of the pipe * @return false in case the header is inconsistent */ static bool upipe_a52f_parse_a52e(struct upipe *upipe) { struct upipe_a52f *upipe_a52f = upipe_a52f_from_upipe(upipe); uint8_t header[A52_SYNCINFO_SIZE]; if (unlikely(!ubase_check(uref_block_extract(upipe_a52f->next_uref, 0, sizeof(header), header)))) return true; /* not enough data */ if (likely(a52e_sync_compare_formats(header, upipe_a52f->sync_header))) { /* identical sync */ upipe_a52f->next_frame_size = a52e_get_frame_size(a52e_get_frmsiz(header)); return true; } /* sample rate */ uint64_t samplerate; switch (a52e_get_fscod(header)) { case A52_FSCOD_48KHZ: samplerate = 48000; break; case A52_FSCOD_441KHZ: samplerate = 44100; break; case A52_FSCOD_32KHZ: samplerate = 32000; break; case A52_FSCOD_RESERVED: switch (a52e_get_fscod2(header)) { case A52E_FSCOD2_24KHZ: samplerate = 24000; break; case A52E_FSCOD2_2205KHZ: samplerate = 22050; break; case A52E_FSCOD2_16KHZ: samplerate = 16000; break; default: upipe_warn(upipe, "reserved fscod2"); return false; } break; default: /* never reached */ return false; } /* frame size */ upipe_a52f->next_frame_size = a52e_get_frame_size(a52e_get_frmsiz(header)); /* channels */ int acmod = a52e_get_acmod(header); int channels = acmod_chans[acmod].nfchans + a52e_get_lfeon(header); uint64_t octetrate = (upipe_a52f->next_frame_size * samplerate + A52_FRAME_SAMPLES - 1) / A52_FRAME_SAMPLES; memcpy(upipe_a52f->sync_header, header, A52_SYNCINFO_SIZE); upipe_a52f->samplerate = samplerate; struct uref *flow_def = upipe_a52f_alloc_flow_def_attr(upipe); if (unlikely(!flow_def)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return false; } UBASE_FATAL(upipe, uref_flow_set_complete(flow_def)) UBASE_FATAL(upipe, uref_flow_set_def(flow_def, "block.eac3.sound.")) UBASE_FATAL(upipe, uref_sound_flow_set_samples(flow_def, A52_FRAME_SAMPLES)) UBASE_FATAL(upipe, uref_sound_flow_set_rate(flow_def, samplerate)) UBASE_FATAL(upipe, uref_sound_flow_set_channels(flow_def, channels)) UBASE_FATAL(upipe, uref_clock_set_latency(flow_def, upipe_a52f->input_latency + UCLOCK_FREQ * A52_FRAME_SAMPLES / samplerate)) UBASE_FATAL(upipe, uref_block_flow_set_octetrate(flow_def, octetrate)) flow_def = upipe_a52f_store_flow_def_attr(upipe, flow_def); if (unlikely(!flow_def)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return false; } upipe_a52f_store_flow_def(upipe, flow_def); return true; }
/** @internal @This parses A/52 header. * * @param upipe description structure of the pipe * @return false in case the header is inconsistent */ static bool upipe_a52f_parse_a52(struct upipe *upipe) { struct upipe_a52f *upipe_a52f = upipe_a52f_from_upipe(upipe); uint8_t header[A52_SYNCINFO_SIZE + 2]; if (unlikely(!ubase_check(uref_block_extract(upipe_a52f->next_uref, 0, sizeof(header), header)))) return true; /* not enough data */ ssize_t next_frame_size = a52_get_frame_size(a52_get_fscod(header), a52_get_frmsizecod(header)); if (!next_frame_size) return false; if (likely(a52_sync_compare_formats(header, upipe_a52f->sync_header) && header[5] == upipe_a52f->sync_header[5] && header[6] == upipe_a52f->sync_header[6])) { /* identical sync */ upipe_a52f->next_frame_size = next_frame_size; return true; } /* sample rate */ uint64_t samplerate; switch (a52_get_fscod(header)) { case A52_FSCOD_48KHZ: samplerate = 48000; break; case A52_FSCOD_441KHZ: samplerate = 44100; break; case A52_FSCOD_32KHZ: samplerate = 32000; break; default: upipe_warn(upipe, "reserved fscod"); return false; } /* frame size */ upipe_a52f->next_frame_size = next_frame_size; /* channels */ int acmod = a52_get_acmod(header); int channels = acmod_chans[acmod].nfchans + ((header[6] >> (4 - acmod_chans[acmod].lfe_offset)) & 1); uint64_t octetrate = a52_bitrate_tab[a52_get_frmsizecod(header)] * 1000 / 8; memcpy(upipe_a52f->sync_header, header, A52_SYNCINFO_SIZE + 2); upipe_a52f->samplerate = samplerate; struct uref *flow_def = upipe_a52f_alloc_flow_def_attr(upipe); if (unlikely(!flow_def)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return false; } UBASE_FATAL(upipe, uref_flow_set_complete(flow_def)) UBASE_FATAL(upipe, uref_flow_set_def(flow_def, "block.ac3.sound.")) UBASE_FATAL(upipe, uref_sound_flow_set_samples(flow_def, A52_FRAME_SAMPLES)) UBASE_FATAL(upipe, uref_sound_flow_set_rate(flow_def, samplerate)) UBASE_FATAL(upipe, uref_sound_flow_set_channels(flow_def, channels)) UBASE_FATAL(upipe, uref_clock_set_latency(flow_def, upipe_a52f->input_latency + UCLOCK_FREQ * A52_FRAME_SAMPLES / samplerate)) UBASE_FATAL(upipe, uref_block_flow_set_octetrate(flow_def, octetrate)) flow_def = upipe_a52f_store_flow_def_attr(upipe, flow_def); if (unlikely(!flow_def)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return false; } upipe_a52f_store_flow_def(upipe, flow_def); return true; }
static int upipe_m3u_reader_process_media(struct upipe *upipe, struct uref *flow_def, const char *line) { struct upipe_m3u_reader *upipe_m3u_reader = upipe_m3u_reader_from_upipe(upipe); if (!ubase_check(uref_flow_match_def(flow_def, M3U_FLOW_DEF)) && !ubase_check(uref_flow_match_def(flow_def, MASTER_FLOW_DEF))) return UBASE_ERR_INVALID; UBASE_RETURN(uref_flow_set_def(flow_def, MASTER_FLOW_DEF)); struct uref *item = uref_sibling_alloc_control(flow_def); if (unlikely(item == NULL)) { upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); return UBASE_ERR_ALLOC; } struct ustring name, value; while (ubase_check(attribute_iterate(&line, &name, &value)) && line) { if (!ustring_cmp_str(name, "URI")) { value = ustring_unframe(value, '"'); char val[value.len + 1]; int err = ustring_cpy(value, val, sizeof (val)); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to copy %.*s", (int)value.len, value.at); continue; } err = uref_m3u_set_uri(item, val); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to set uri %s", val); continue; } } else if (!ustring_cmp_str(name, "TYPE")) { char val[value.len + 1]; int err = ustring_cpy(value, val, sizeof (val)); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to copy %.*s", (int)value.len, value.at); continue; } err = uref_m3u_master_set_media_type(item, val); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to set media type %s", val); continue; } } else if (!ustring_cmp_str(name, "DEFAULT")) { if (!ustring_cmp_str(value, "YES")) { int err = uref_m3u_master_set_media_default(item); if (unlikely(!ubase_check(err))) continue; } else if (ustring_cmp_str(value, "NO")) { upipe_warn_va(upipe, "invalid DEFAULT value %.*s", (int)value.len, value.at); continue; } } else if (!ustring_cmp_str(name, "AUTOSELECT")) { if (!ustring_cmp_str(value, "YES")) { int err = uref_m3u_master_set_media_autoselect(item); if (unlikely(!ubase_check(err))) continue; } else if (ustring_cmp_str(value, "NO")) { upipe_warn_va(upipe, "invalid AUTOSELECT value %.*s", (int)value.len, value.at); continue; } } else if (!ustring_cmp_str(name, "NAME")) { value = ustring_unframe(value, '"'); char val[value.len + 1]; int err = ustring_cpy(value, val, sizeof (val)); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to copy %.*s", (int)value.len, value.at); continue; } err = uref_m3u_master_set_media_name(item, val); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to set media name %s", val); continue; } } else if (!ustring_cmp_str(name, "GROUP-ID")) { value = ustring_unframe(value, '"'); char val[value.len + 1]; int err = ustring_cpy(value, val, sizeof (val)); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to copy %.*s", (int)value.len, value.at); continue; } err = uref_m3u_master_set_media_group(item, val); if (unlikely(!ubase_check(err))) { upipe_warn_va(upipe, "fail to set group id %s", val); continue; } } else { upipe_warn_va(upipe, "ignoring attribute %.*s (%.*s)", (int)name.len, name.at, (int)value.len, value.at); } } ulist_add(&upipe_m3u_reader->items, uref_to_uchain(item)); return UBASE_ERR_NONE; }