Exemplo n.º 1
0
static void upipe_dump_line(struct upipe *upipe, unsigned int at,
                            uint8_t *line, int size)
{
    char hex[16 * 2 + 16 + 1];
    char *tmp = hex;

    if (!size)
        return;

    for (unsigned i = 0; i < 16; i++) {
        const char *sep = i ? (i == 8 ? "  " : " "): "";
        if (i < size)
            tmp += snprintf(tmp, hex + sizeof (hex) - tmp,
                            "%s%02x", sep, line[i]);
        else
            tmp += snprintf(tmp, hex + sizeof (hex) - tmp, "%s  ", sep);
        assert(tmp < hex + sizeof (hex));
    }

    char ascii[16 + 1];
    tmp = ascii;
    for (unsigned i = 0; i < size; i++) {
        tmp += snprintf(tmp, ascii + sizeof (ascii) - tmp,
                        "%c", isprint(line[i]) ? line[i] : '.');
        assert(tmp < ascii + sizeof (ascii));
    }

    upipe_notice_va(upipe, "hexdump: %08x  %s  |%s|", at, hex, ascii);
}
Exemplo n.º 2
0
static void upipe_dveo_asi_sink_stats(struct upipe *upipe)
{
    struct upipe_dveo_asi_sink *upipe_dveo_asi_sink = upipe_dveo_asi_sink_from_upipe(upipe);
    int fd = upipe_dveo_asi_sink->fd;

    static uint64_t sum;
    sum += 188;
    //upipe_notice_va(upipe, "Wrote %"PRIu64" bytes into the card", sum);

    int val;

    if (ioctl(fd, ASI_IOC_TXGETEVENTS, &val) < 0)
        upipe_err_va(upipe, "ioctl TXGETEVENTS failed (%m)");
    else {
        if (val & ASI_EVENT_TX_BUFFER)
            upipe_notice(upipe, "driver transmit buffer queue underrun");
        if (val & ASI_EVENT_TX_FIFO)
            upipe_notice(upipe, "onboard transmit FIFO underrun");
        if (val & ASI_EVENT_TX_DATA) {
            upipe_notice(upipe, "transmit data status change");
            if (ioctl(fd, ASI_IOC_TXGETTXD, &val) < 0)
                upipe_err_va(upipe, "ioctl TXGETTXDfailed (%m)");
            else
                upipe_notice_va(upipe, "transmitting: %d", val);
        }
    }

    if (ioctl(fd, ASI_IOC_TXGETBUFLEVEL, &val) < 0)
        upipe_err_va(upipe, "ioctl TXGETBUFLEVEL failed (%m)");
    else {
        static int old;
#define MARGIN 2
        if ((val - MARGIN) >  old || (val + MARGIN) < old) {
            float secs = (float)val * 6 * 196 * 8 / 10500000;
            upipe_notice_va(upipe, "buf level %d -> %.2fs", val, secs);
            old = val;
        }
    }

    if (ioctl(fd, ASI_IOC_TXGETBYTECOUNT, &val) < 0)
        upipe_err_va(upipe, "ioctl TXGETBYTECOUNTfailed (%m)");
    else {
        /*static uint64_t byte_sum;
        byte_sum += val;
        upipe_notice_va(upipe, "byte count %d -> %"PRIu64, val, byte_sum);*/
    }
}
Exemplo n.º 3
0
static void upipe_dveo_asi_sink_close(struct upipe *upipe)
{
    struct upipe_dveo_asi_sink *upipe_dveo_asi_sink = upipe_dveo_asi_sink_from_upipe(upipe);

    if (unlikely(upipe_dveo_asi_sink->fd != -1)) {
        upipe_notice_va(upipe, "closing card %i", upipe_dveo_asi_sink->card_idx);
        ubase_clean_fd(&upipe_dveo_asi_sink->fd);
    }
    upipe_dveo_asi_sink_set_upump(upipe, NULL);
}
Exemplo n.º 4
0
/** @internal @This changes the rotate interval
 *
 * @param upipe description structure of the pipe
 * @param rotate new rotate interval
 * @return false in case of error
 */
static bool  _upipe_multicat_probe_set_rotate(struct upipe *upipe, uint64_t rotate)
{
    struct upipe_multicat_probe *upipe_multicat_probe = upipe_multicat_probe_from_upipe(upipe);
    if (unlikely(rotate < 1)) {
        upipe_warn_va(upipe, "invalid rotate interval (%"PRIu64" < 1)", rotate);
        return false;
    }
    upipe_multicat_probe->rotate = rotate;
    upipe_notice_va(upipe, "setting rotate: %"PRIu64, rotate);
    return true;
}
Exemplo n.º 5
0
/** @internal @This interpolates the PCRs for packets without a PCR.
 *
 * @param upipe description structure of the pipe
 * @param uref uref structure
 * @param upump_p reference to pump that generated the buffer
 */
static void upipe_ts_pcr_interpolator_input(struct upipe *upipe, struct uref *uref,
                                  struct upump **upump_p)
{
    struct upipe_ts_pcr_interpolator *upipe_ts_pcr_interpolator = upipe_ts_pcr_interpolator_from_upipe(upipe);
    bool discontinuity = ubase_check(uref_flow_get_discontinuity(uref));
    if (discontinuity) {
        upipe_ts_pcr_interpolator->last_pcr = 0;
        upipe_ts_pcr_interpolator->packets = 0;
        upipe_ts_pcr_interpolator->pcr_packets = 0;
        upipe_ts_pcr_interpolator->pcr_delta = 0;
        upipe_ts_pcr_interpolator->discontinuity = true;
        upipe_notice_va(upipe, "Clearing state");
    }

    upipe_ts_pcr_interpolator->packets++;

    uint64_t pcr_prog = 0;
    uref_clock_get_cr_prog(uref, &pcr_prog);

    if (pcr_prog) {
        uint64_t delta = pcr_prog - upipe_ts_pcr_interpolator->last_pcr;
        upipe_ts_pcr_interpolator->last_pcr = pcr_prog;

        upipe_verbose_va(upipe,
                "pcr_prog %"PRId64" offset %"PRId64" stored offset %"PRIu64" bitrate %"PRId64" bps",
                pcr_prog, delta,
                upipe_ts_pcr_interpolator->pcr_delta,
                INT64_C(27000000) * upipe_ts_pcr_interpolator->packets * 188 * 8 / delta);

        if (upipe_ts_pcr_interpolator->pcr_delta)
            upipe_ts_pcr_interpolator->pcr_packets = upipe_ts_pcr_interpolator->packets;

        upipe_ts_pcr_interpolator->pcr_delta = delta;
        upipe_ts_pcr_interpolator->packets = 0;
    } else if (upipe_ts_pcr_interpolator->pcr_packets) {
        uint64_t offset = upipe_ts_pcr_interpolator->pcr_delta *
                    upipe_ts_pcr_interpolator->packets / upipe_ts_pcr_interpolator->pcr_packets;
        uint64_t prog = upipe_ts_pcr_interpolator->last_pcr + offset;
        uref_clock_set_date_prog(uref, prog, UREF_DATE_CR);
        upipe_throw_clock_ts(upipe, uref);
    }

    if (!upipe_ts_pcr_interpolator->pcr_packets) {
        uref_free(uref);
        return;
    }

    if (upipe_ts_pcr_interpolator->discontinuity) {
        uref_flow_set_discontinuity(uref);
        upipe_ts_pcr_interpolator->discontinuity = false;
    }
    upipe_ts_pcr_interpolator_output(upipe, uref, upump_p);
}
Exemplo n.º 6
0
static void keyhandler(struct upipe *upipe, unsigned long key)
{
    switch (key) {
        case 27:
        case 'q': {
            upipe_notice_va(upipe, "exit key pressed (%d), exiting", key);
            exit(0);
        }
        default:
            upipe_dbg_va(upipe, "key pressed (%d)", key);
            break;
    }
}
Exemplo n.º 7
0
/** @internal @This catches events of the glx pipe.
 *
 * @param uprobe pointer to probe
 * @param upipe pointer to pipe throwing the event
 * @param event event thrown
 * @param args optional event-specific parameters
 * @return an error code
 */
static int upipe_glxplayer_catch_glx(struct uprobe *uprobe,
                                     struct upipe *upipe,
                                     int event, va_list args)
{
    switch (event) {
        case UPROBE_GLX_SINK_KEYPRESS: {
            struct upipe_glxplayer *glxplayer =
                container_of(uprobe, struct upipe_glxplayer,
                             uprobe_glx_s);
            unsigned int signature = va_arg(args, unsigned int);
            assert(signature == UPIPE_GLX_SINK_SIGNATURE);
            unsigned long key = va_arg(args, unsigned long);

            switch (key) {
                case 27:
                case 'q': {
                    upipe_notice_va(upipe, "exit key pressed (%d), exiting",
                                    key);
                    upipe_release(glxplayer->upipe_src_xfer);
                    upipe_mgr_release(glxplayer->src_xfer);
                    break;
                }
                case ' ': {
                    if (glxplayer->trickp) {
                        if ( (glxplayer->paused = !glxplayer->paused) ) {
                            upipe_notice(upipe, "Playback paused");
                            struct urational rate = { .num = 0, .den = 0 };
                            upipe_trickp_set_rate(glxplayer->upipe_trickp, rate);
                        } else {
                            upipe_notice(upipe, "Playback resumed");
                            struct urational rate = { .num = 1, .den = 1 };
                            upipe_trickp_set_rate(glxplayer->upipe_trickp, rate);
                        }
                    }
                    break;
                }
                default:
                    upipe_dbg_va(upipe, "key pressed (%d)", key);
                    break;
            }
            return UBASE_ERR_NONE;
        }
        case UPROBE_GLX_SINK_KEYRELEASE:
            return UBASE_ERR_NONE;
        default:
            break;
    }
    return uprobe_throw_next(uprobe, upipe, event, args);
}
Exemplo n.º 8
0
/** @internal @This creates a new audioqueue
 * @param upipe description structure of the pipe
 * @param flow description structure of the flow
 * @return an error code
 */
static int upipe_osx_audioqueue_sink_set_flow_def(struct upipe *upipe,
        struct uref *flow)
{
    OSStatus status;
    uint64_t sample_rate = 0; /* hush gcc */
    uint8_t channels = 0;
    uint8_t sample_size = 0;
    struct AudioStreamBasicDescription fmt;
    struct upipe_osx_audioqueue_sink *osx_audioqueue =
        upipe_osx_audioqueue_sink_from_upipe(upipe);

    if (unlikely(osx_audioqueue->queue)) {
        upipe_osx_audioqueue_sink_remove(upipe);
    }

    /* retrieve flow format information */
    uref_sound_flow_get_rate(flow, &sample_rate);
    uref_sound_flow_get_sample_size(flow, &sample_size);
    uref_sound_flow_get_channels(flow, &channels);

    /* build format description */
    memset(&fmt, 0, sizeof(struct AudioStreamBasicDescription));
    fmt.mSampleRate = sample_rate;
    fmt.mFormatID = kAudioFormatLinearPCM;
    fmt.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    fmt.mFramesPerPacket = 1;
    fmt.mChannelsPerFrame = channels;
    fmt.mBytesPerPacket = fmt.mBytesPerFrame = sample_size * channels;
    fmt.mBitsPerChannel = sample_size * 8;

    /* create queue */
    status = AudioQueueNewOutput(&fmt, upipe_osx_audioqueue_sink_cb, upipe,
                                 NULL, kCFRunLoopCommonModes, 0, &osx_audioqueue->queue);
    if (unlikely(status == kAudioFormatUnsupportedDataFormatError)) {
        upipe_warn(upipe, "unsupported data format");
        return UBASE_ERR_EXTERNAL;
    }

    /* change volume */
    AudioQueueSetParameter(osx_audioqueue->queue, kAudioQueueParam_Volume,
                           osx_audioqueue->volume);

    /* start queue ! */
    AudioQueueStart(osx_audioqueue->queue, NULL);
    upipe_notice_va(upipe, "audioqueue started (%uHz, %hhuch, %db)",
                    sample_rate, channels, sample_size*8);

    return UBASE_ERR_NONE;
}
Exemplo n.º 9
0
/** @This frees a upipe.
 *
 * @param upipe description structure of the pipe
 */
static void upipe_dveo_asi_sink_free(struct upipe *upipe)
{
    struct upipe_dveo_asi_sink *upipe_dveo_asi_sink = upipe_dveo_asi_sink_from_upipe(upipe);

    if (likely(upipe_dveo_asi_sink->fd != -1)) {
        upipe_notice_va(upipe, "closing device %d", upipe_dveo_asi_sink->card_idx);
        close(upipe_dveo_asi_sink->fd);
    }
    upipe_throw_dead(upipe);

    upipe_dveo_asi_sink_clean_upump(upipe);
    upipe_dveo_asi_sink_clean_upump_mgr(upipe);
    upipe_dveo_asi_sink_clean_input(upipe);
    upipe_dveo_asi_sink_clean_urefcount(upipe);
    upipe_dveo_asi_sink_free_void(upipe);
}
Exemplo n.º 10
0
static void upipe_dump_input(struct upipe *upipe, struct uref *uref,
                             struct upump **upump_p)
{
    struct upipe_dump *upipe_dump = upipe_dump_from_upipe(upipe);

    uref_dump(uref, upipe->uprobe);

    size_t total_size;
    ubase_assert(uref_block_size(uref, &total_size));

    upipe_notice_va(upipe, "dumping ubuf %p of size %zu",
                    uref->ubuf, total_size);

    if (upipe_dump->max_len != (size_t)-1 &&
        total_size > upipe_dump->max_len)
        total_size = upipe_dump->max_len;

    unsigned int count = 0;
    uint8_t line[16];

    int offset = 0;
    while (total_size) {
        const uint8_t *buf;
        int size = total_size;

        ubase_assert(uref_block_read(uref, offset, &size, &buf));
        assert(size != 0);

        total_size -= size;

        for (unsigned i = 0; i < size; i++, count++) {
            line[count % 16] = buf[i];

            if (!((count + 1) % 16) || (!total_size && (i + 1 == size)))
                upipe_dump_line(upipe, count - (count % 16),
                                line, (count % 16) + 1);
        }

        ubase_assert(uref_block_unmap(uref, offset));
        offset += size;
    }
#define UPIPE_DUMP_SEP \
    "--------------------------------------------" \
    "-------------------------------------------"
    upipe_notice(upipe, UPIPE_DUMP_SEP);
    upipe_dump_output(upipe, uref, upump_p);
}
Exemplo n.º 11
0
/** @This frees a upipe.
 *
 * @param upipe description structure of the pipe
 */
static void upipe_fsink_free(struct upipe *upipe)
{
    struct upipe_fsink *upipe_fsink = upipe_fsink_from_upipe(upipe);
    if (likely(upipe_fsink->fd != -1)) {
        if (likely(upipe_fsink->path != NULL))
            upipe_notice_va(upipe, "closing file %s", upipe_fsink->path);
        close(upipe_fsink->fd);
    }
    upipe_throw_dead(upipe);

    free(upipe_fsink->path);
    upipe_fsink_clean_uclock(upipe);
    upipe_fsink_clean_upump(upipe);
    upipe_fsink_clean_upump_mgr(upipe);
    upipe_fsink_clean_input(upipe);
    upipe_fsink_clean_urefcount(upipe);
    upipe_fsink_free_void(upipe);
}
Exemplo n.º 12
0
/** @internal @This provides a ubuf_mgr request.
 *
 * @param upipe description structure of the pipe
 * @param flow_format amended flow format
 * @return an error code
 */
static int upipe_audiobar_check_ubuf_mgr(struct upipe *upipe,
                                         struct uref *flow_format)
{
    struct upipe_audiobar *upipe_audiobar = upipe_audiobar_from_upipe(upipe);
    if (flow_format == NULL)
        return UBASE_ERR_NONE;

    upipe_audiobar_store_flow_def(upipe, flow_format);
    UBASE_RETURN(uref_pic_flow_get_hsize(flow_format, &upipe_audiobar->hsize))
    UBASE_RETURN(uref_pic_flow_get_vsize(flow_format, &upipe_audiobar->vsize))
    upipe_audiobar->chan_width =
        upipe_audiobar->hsize / upipe_audiobar->channels;
    upipe_audiobar->chan_width -= upipe_audiobar->chan_width % 2;
    if (unlikely(upipe_audiobar->chan_width < 16)) {
        upipe_warn_va(upipe,
                "channel width is too small to have a separation (%"PRIu64")",
                upipe_audiobar->chan_width);
    }
    upipe_audiobar->sep_width = upipe_audiobar->chan_width / 4;
    upipe_audiobar->sep_width -= upipe_audiobar->sep_width % 4;
    upipe_audiobar->pad_width = upipe_audiobar->hsize -
        upipe_audiobar->chan_width * upipe_audiobar->channels;
    upipe_notice_va(upipe,
            "setting up chan %"PRIu64" sep %"PRIu64" pad %"PRIu64,
            upipe_audiobar->chan_width, upipe_audiobar->sep_width,
            upipe_audiobar->pad_width);

    bool was_buffered = !upipe_audiobar_check_input(upipe);
    upipe_audiobar_output_input(upipe);
    upipe_audiobar_unblock_input(upipe);
    if (was_buffered && upipe_audiobar_check_input(upipe)) {
        /* All packets have been output, release again the pipe that has been
         * used in @ref upipe_audiobar_input. */
        upipe_release(upipe);
    }
    return UBASE_ERR_NONE;
}
Exemplo n.º 13
0
/** @internal @This configures a new codec context
 *
 * @param upipe description structure of the pipe
 * @param codec avcodec description structure
 * @param extradata pointer to extradata buffer
 * @param extradata_size extradata size
 * @return false if the buffer couldn't be accepted
 */
static bool upipe_avcdec_open_codec(struct upipe *upipe, AVCodec *codec,
                                    uint8_t *extradata, int extradata_size)
{
    AVCodecContext *context = NULL;
    struct upipe_avcdec *upipe_avcdec = upipe_avcdec_from_upipe(upipe);
    assert(upipe);

    /* close previously opened context */
    if (unlikely(upipe_avcdec->context)) {
        /* first send empty packet to flush retained frames */
        upipe_dbg(upipe, "flushing frames in decoder");
        while (upipe_avcdec_process_buf(upipe, NULL, 0, NULL));

        /* now close codec and free extradata if any */
        upipe_notice_va(upipe, "avcodec context (%s) closed (%d)",
                    upipe_avcdec->context->codec->name, upipe_avcdec->counter);
        avcodec_close(upipe_avcdec->context);
        if (upipe_avcdec->context->extradata_size > 0) {
            free(upipe_avcdec->context->extradata);
        }
        av_free(upipe_avcdec->context);
        upipe_avcdec->context = NULL;
        upipe_avcdec_store_flow_def(upipe, NULL);
    }

    /* just closing, that's all */
    if (!codec) {
        upipe_release(upipe);
        return false;
    }

    /* allocate and configure codec context */
    context = avcodec_alloc_context3(codec);
    if (unlikely(!context)) {
        upipe_throw_aerror(upipe);
        upipe_release(upipe);
        return false;
    }
    context->opaque = upipe;
    context->extradata = extradata;
    context->extradata_size = extradata_size;

    switch (codec->type) {
        case AVMEDIA_TYPE_VIDEO: {
            if (upipe_avcdec->lowres > codec->max_lowres) {
                upipe_warn_va(upipe, "Unsupported lowres (%d > %hhu), setting to %hhu",
                    upipe_avcdec->lowres, codec->max_lowres, codec->max_lowres);
                upipe_avcdec->lowres = codec->max_lowres;
            }

            context->get_buffer = upipe_avcdec_get_buffer;
            context->release_buffer = upipe_avcdec_release_buffer;
            context->flags |= CODEC_FLAG_EMU_EDGE;
            context->lowres = upipe_avcdec->lowres;
            context->skip_loop_filter = AVDISCARD_ALL;

            break;
        }
        case AVMEDIA_TYPE_AUDIO: {
            context->get_buffer = upipe_avcdec_get_buffer_audio;
            break;
        }
        default: {
            av_free(context);
            upipe_err_va(upipe, "Unsupported media type (%d)", codec->type);
            upipe_release(upipe);
            return false;
            break;
        }
    }

    /* open new context */
    if (unlikely(avcodec_open2(context, codec, NULL) < 0)) {
        upipe_warn(upipe, "could not open codec");
        av_free(context);
        upipe_release(upipe);
        return false;
    }

    upipe_avcdec->context = context;
    upipe_avcdec->counter = 0;
    upipe_notice_va(upipe, "codec %s (%s) %d opened", codec->name, 
            codec->long_name, codec->id);

    upipe_release(upipe);
    return true;
}
Exemplo n.º 14
0
/** @internal @This asks to open the given file.
 *
 * @param upipe description structure of the pipe
 * @param path relative or absolute path of the file
 * @param mode mode of opening the file
 * @return an error code
 */
static int _upipe_fsink_set_path(struct upipe *upipe, const char *path,
                                 enum upipe_fsink_mode mode)
{
    struct upipe_fsink *upipe_fsink = upipe_fsink_from_upipe(upipe);

    if (unlikely(upipe_fsink->fd != -1)) {
        if (likely(upipe_fsink->path != NULL))
            upipe_notice_va(upipe, "closing file %s", upipe_fsink->path);
        close(upipe_fsink->fd);
    }
    free(upipe_fsink->path);
    upipe_fsink->path = NULL;
    upipe_fsink_set_upump(upipe, NULL);
    if (!upipe_fsink_check_input(upipe))
        /* Release the pipe used in @ref upipe_fsink_input. */
        upipe_release(upipe);

    if (unlikely(path == NULL))
        return UBASE_ERR_NONE;

    upipe_fsink_check_upump_mgr(upipe);

    const char *mode_desc = NULL; /* hush gcc */
    int flags;
    switch (mode) {
        case UPIPE_FSINK_NONE:
            flags = 0;
            break;
        case UPIPE_FSINK_APPEND:
            mode_desc = "append";
            flags = O_CREAT;
            break;
        case UPIPE_FSINK_OVERWRITE:
            mode_desc = "overwrite";
            flags = O_CREAT | O_TRUNC;
            break;
        case UPIPE_FSINK_CREATE:
            mode_desc = "create";
            flags = O_CREAT | O_EXCL;
            break;
        default:
            upipe_err_va(upipe, "invalid mode %d", mode);
            return UBASE_ERR_INVALID;
    }
    upipe_fsink->fd = open(path, O_WRONLY | O_NONBLOCK | O_CLOEXEC | flags,
                           S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (unlikely(upipe_fsink->fd == -1)) {
        upipe_err_va(upipe, "can't open file %s (%s)", path, mode_desc);
        return UBASE_ERR_EXTERNAL;
    }
    switch (mode) {
        /* O_APPEND seeks on each write, so use this instead */
        case UPIPE_FSINK_APPEND:
            if (unlikely(lseek(upipe_fsink->fd, 0, SEEK_END) == -1)) {
                upipe_err_va(upipe, "can't append to file %s (%s)", path, mode_desc);
                close(upipe_fsink->fd);
                upipe_fsink->fd = -1;
                return UBASE_ERR_EXTERNAL;
            }
            break;
        default:
            break;
    }

    upipe_fsink->path = strdup(path);
    if (unlikely(upipe_fsink->path == NULL)) {
        close(upipe_fsink->fd);
        upipe_fsink->fd = -1;
        upipe_throw_fatal(upipe, UBASE_ERR_ALLOC);
        return UBASE_ERR_ALLOC;
    }
    if (!upipe_fsink_check_input(upipe))
        /* Use again the pipe that we previously released. */
        upipe_use(upipe);
    upipe_notice_va(upipe, "opening file %s in %s mode",
                    upipe_fsink->path, mode_desc);
    return UBASE_ERR_NONE;
}
Exemplo n.º 15
0
/** @internal @This asks to open the given device.
 *
 * @param upipe description structure of the pipe
 * @param path relative or absolute path of the file
 * @return an error code
 */
static int upipe_dveo_asi_sink_open(struct upipe *upipe)
{
#define BYPASS_MODE 1

    struct upipe_dveo_asi_sink *upipe_dveo_asi_sink = upipe_dveo_asi_sink_from_upipe(upipe);
    char path[20], sys[50], buf[20];
    memset(buf, 0, sizeof(buf));

    static const char dev_fmt[] = "/dev/asitx%u";
    static const char sys_fmt[] = "/sys/class/asi/asitx%u/%s";
    static const char dvbm_sys_fmt[] = "/sys/class/dvbm/%u/%s";

    snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "timestamps");
    snprintf(buf, sizeof(buf), "%u\n", 2);
    if (util_write(sys, buf, sizeof(buf)) < 0) {
        upipe_err_va(upipe, "Couldn't set timestamp mode (%m)");
        return UBASE_ERR_EXTERNAL;
    }

    snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "bufsize");
    snprintf(buf, sizeof(buf), "%u\n", 6*(188+8)); /* minimum is 1024 */
    if (util_write(sys, buf, sizeof(buf)) < 0) {
        upipe_err_va(upipe, "Couldn't set buffer size (%m)");
        return UBASE_ERR_EXTERNAL;
    }

    snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "buffers");
    snprintf(buf, sizeof(buf), "%u\n", 500);
    if (util_write(sys, buf, sizeof(buf)) < 0) {
        upipe_err_va(upipe, "Couldn't set # of buffers (%m)");
        return UBASE_ERR_EXTERNAL;
    }

    snprintf(path, sizeof(path), dev_fmt, upipe_dveo_asi_sink->card_idx);
    int fd = open(path, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
    if (unlikely(fd < 0)) {
        upipe_err_va(upipe, "can't open file %s (%m)", path);
        return UBASE_ERR_EXTERNAL;
    }

    snprintf(sys, sizeof(sys), dvbm_sys_fmt, upipe_dveo_asi_sink->card_idx, "bypass_mode");
    snprintf(buf, sizeof(buf), "%u", BYPASS_MODE);
    util_write(sys, buf, sizeof(buf)); /* Not all cards have this so don't fail */

    unsigned int cap;
    if (ioctl(fd, ASI_IOC_TXGETCAP, &cap) < 0) {
        upipe_err_va(upipe, "can't get tx caps (%m)");
        goto error;
    }

    unsigned long int bufsize;
    snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "bufsize");
    if (util_strtoul(sys, &bufsize) < 0) {
        upipe_err(upipe, "Couldn't read buffer size");
        goto error;
    }

    unsigned long int mode;
    snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "mode");
    if (util_strtoul(sys, &mode) < 0) {
        upipe_err(upipe, "Couldn't read buffer size");
        goto error;
    }

    switch (mode) {
        case ASI_CTL_TX_MODE_MAKE204:
            upipe_dbg(upipe, "Appending 0x00 bytes to make 204 bytes packets");
        case ASI_CTL_TX_MODE_188:
        case ASI_CTL_TX_MODE_204:
            break;
        default:
            upipe_err_va(upipe, "Unknown TX mode %lu", mode);
            goto error;
    }

    unsigned long int clock_source = 0;
    if (cap & ASI_CAP_TX_SETCLKSRC) {
        snprintf(sys, sizeof(sys), sys_fmt, upipe_dveo_asi_sink->card_idx, "clock_source");
        if (util_strtoul(sys, &clock_source) < 0) {
            upipe_err(upipe, "Couldn't read clock source");
            goto error;
        }
    }

    switch (clock_source) {
        case ASI_CTL_TX_CLKSRC_ONBOARD:
            upipe_dbg(upipe, "Using onboard oscillator");
            break;
        case ASI_CTL_TX_CLKSRC_EXT:
            upipe_dbg(upipe, "Using external reference clock");
            break;
        case ASI_CTL_TX_CLKSRC_RX:
            upipe_dbg(upipe, "Using recovered receive clock");
            break;
        default:
            upipe_dbg(upipe, "Unknown clock source");
                break;
    }

    if (!(cap & ASI_CAP_TX_TIMESTAMPS)) {
        upipe_err(upipe, "Device does not support timestamps");
        goto error;
    }

    upipe_dveo_asi_sink->fd = fd;

    upipe_notice_va(upipe, "opening file %s", path);
    return UBASE_ERR_NONE;

error:
    close(fd);
    return UBASE_ERR_EXTERNAL;
}