summaryrefslogtreecommitdiff
path: root/src/codec/ffmpeg/encoder.c
blob: 641acc53a01b4ea95f42e6f01ba953caecc72e54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#define AL_LOG_SECTION "ff_encoder"
#include <al/log.h>
#include <al/lib.h>

#include "encoder.h"

#define FRAME_DURATION 20
#define SAMPLES_PER_FRAME (48000 / 1000.0 * FRAME_DURATION)
#define CHANNELS 2

static void free_frame_data(struct camu_ff_encoder *enc)
{
    if (enc->frame && enc->frame->nb_samples > 0) av_freep(&enc->frame->data[0]);
}

bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char *ext, u32 bitrate)
{
    al_memset(enc, 0, sizeof(struct camu_ff_encoder));

    const AVCodec *codec = avcodec_find_encoder_by_name(encoder_name);

    if (!codec) {
        log_error("Failed to find codec by name %s.", encoder_name);
        goto err;
    }

    enc->codec_context = avcodec_alloc_context3(codec);

    if (!enc->codec_context) {
        log_error("Failed to alloc codec context.");
        goto err;
    }

    enc->codec_context->sample_rate = 48000;
    camu_default_channel_layout(&AV_CODECPAR_CHANNEL_LAYOUT(enc->codec_context), CHANNELS);
    enc->codec_context->sample_fmt = AV_SAMPLE_FMT_FLT;
    enc->codec_context->bit_rate = bitrate;
    enc->codec_context->frame_size = SAMPLES_PER_FRAME;
    enc->codec_context->time_base = (AVRational){ 1, enc->codec_context->sample_rate };
    enc->codec_context->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

    enc->output_format = av_guess_format(encoder_name, ext, NULL);
    if (enc->output_format && enc->output_format->flags & AVFMT_GLOBALHEADER) {
        enc->codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    AVDictionary *opts = NULL;
    char kbps[16];
    al_snprintf(kbps, sizeof(kbps), "%uk", bitrate / 1000);
    av_dict_set(&opts, "b", kbps, 0);
    av_dict_set(&opts, "vbr", "off", 0);
    av_dict_set(&opts, "compression_level", "10", 0);
    av_dict_set(&opts, "frame_duration", "20", 0);
    av_dict_set(&opts, "application", "audio", 0);
    if (avcodec_open2(enc->codec_context, codec, &opts) < 0) {
        log_error("Failed to open codec.");
        av_dict_free(&opts);
        goto err;
    }
    av_dict_free(&opts);

    log_info("Encoder: %s (%s) %sbps.", codec->name,
        codec->long_name ? codec->long_name : codec->name, kbps);

    AVCodecContext *out_codec = enc->codec_context;
    AVFrame *frame = av_frame_alloc();
    frame->format = out_codec->sample_fmt;
    frame->sample_rate = out_codec->sample_rate;
    camu_copy_channel_layout(&AV_CODECPAR_CHANNEL_LAYOUT(frame), &AV_CODECPAR_CHANNEL_LAYOUT(out_codec));
    av_samples_alloc(frame->data, NULL, AV_CODECPAR_CHANNELS(out_codec),
        SAMPLES_PER_FRAME, (enum AVSampleFormat)frame->format, 1);
    frame->nb_samples = SAMPLES_PER_FRAME;
    frame->pts = 0;

    enc->fmt.format = frame->format;
    enc->fmt.sample_rate = frame->sample_rate;
    camu_copy_channel_layout(&enc->fmt.channel_layout, &AV_CODECPAR_CHANNEL_LAYOUT(frame));
    enc->fmt.channel_count = AV_CODECPAR_CHANNELS(frame);

    enc->frame = frame;

    return true;
err:
    camu_ff_encoder_close(enc);
    return false;
}

void camu_ff_encoder_push(struct camu_ff_encoder *enc, u8 **data, s32 sample_count)
{
    al_assert(sample_count == SAMPLES_PER_FRAME * CHANNELS);
    enc->frame->data[0] = data[0];
    s32 ret = avcodec_send_frame(enc->codec_context, enc->frame);
    if (ret < 0 && ret != AVERROR_EOF) {
        log_error("Error sending frame to the encoder (%s).", av_err2str(ret));
        return;
    }
    AVPacket pkt = { 0 };
    for (;;) {
        ret = avcodec_receive_packet(enc->codec_context, &pkt);
        if (ret < 0)  {
            if (!(ret = AVERROR_EOF || ret == AVERROR(EAGAIN))) {
                log_error("Error receiving packet from the encoder (%s).", av_err2str(ret));
            }
            return;
        }
        enc->callback(enc->userdata, &pkt);
        av_packet_unref(&pkt);
        enc->frame->pts += SAMPLES_PER_FRAME;
    }
}

void camu_ff_encoder_reset(struct camu_ff_encoder *enc)
{
    avcodec_flush_buffers(enc->codec_context);
}

void camu_ff_encoder_close(struct camu_ff_encoder *enc)
{
    if (enc->codec_context) avcodec_free_context(&enc->codec_context);
    free_frame_data(enc);
    if (enc->frame) av_frame_free(&enc->frame);
}