summaryrefslogtreecommitdiff
path: root/src/liana/handlers/codec_client.c
blob: c3dded100bef2a9b0c76c4871b2bfb231c0c9c24 (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include "../../codec/codec.h"
#include "../../codec/codecs.h"
#ifdef CAMU_HAVE_FFMPEG
#include "../../codec/ffmpeg/packet_ext.h"
#endif

#include "../vcr.h"
#include "../client.h"

#include "codec.h"

static void data_callback(void *userdata, struct camu_codec_frame *frame)
{
    struct lia_codec_client *codec = (struct lia_codec_client *)userdata;
    codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_DATA, codec->handler.stream, frame);
}

static bool codec_client_init(struct lia_client_handler *handler, struct camu_renderer *renderer,
    struct camu_codec_stream *stream)
{
    struct lia_codec_client *codec = (struct lia_codec_client *)handler;
    codec->handler.stream = stream;
    if (stream->type == CAMU_STREAM_AUDIO || stream->type == CAMU_STREAM_VIDEO) {
        codec->dec = stream->codec_info->create_decoder();
        if (!codec->dec->init(codec->dec, renderer, stream, data_callback, codec)) {
            return false;
        }
    }
    return true;
}

#ifdef CAMU_HAVE_FFMPEG
static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt)
{
    s32 ret = codec->dec->push_av_packet(codec->dec, pkt);
    while (ret == AVERROR(EAGAIN)) {
        codec->dec->process(codec->dec);
        ret = codec->dec->push_av_packet(codec->dec, pkt);
    }
    return ret == CAMU_OK;
}

static void passthrough_subtitle(struct lia_codec_client *codec, AVPacket *pkt)
{
    struct camu_codec_stream *stream = codec->handler.stream;
    struct camu_codec_packet packet = { .av.pkt = pkt };
    codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_SUBTITLE, stream, &packet);
}
#endif

static bool push_packet(struct lia_codec_client *codec, struct nn_buffer *buffer)
{
    struct camu_codec_packet packet;
    packet.buffer = buffer;
    s32 ret = codec->dec->push(codec->dec, &packet);
    return ret == CAMU_OK;
}

static bool codec_client_handle_packet(struct lia_client_handler *handler, struct nn_packet *packet)
{
    struct lia_codec_client *codec = (struct lia_codec_client *)handler;

    // NULL packet = flush.
    if (!packet) {
        if (codec->dec) {
            // Flush always returns success.
            switch (codec->dec->mode) {
            case CAMU_NORMAL:
                codec->dec->push(codec->dec, NULL);
                break;
#ifdef CAMU_HAVE_FFMPEG
            case CAMU_FFMPEG_COMPAT:
                push_av_packet(codec, NULL);
                break;
#endif
            }
            // process() could still error.
            s32 ret = codec->dec->process(codec->dec);
            codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_EOF, codec->handler.stream, NULL);
            return ret == CAMU_ERR_EOF;
        } else {
            return true;
        }
    }

    // Push packet.
    u32 rindex = packet->rindex;
    bool success;
    u8 mode = nn_packet_read_u8(packet);
    // @TODO: default: shouldn't be a case. We should check for an invalid packet.
    switch (mode) {
    case CAMU_NORMAL: {
        if (codec->dec) {
            if (packet->opaque) {
                success = push_packet(codec, (struct nn_buffer *)packet->opaque);
            } else {
                struct nn_buffer buffer;
                nn_packet_read_buffer(packet, &buffer);
                success = push_packet(codec, &buffer);
            }
        } else {
            success = true;
        }
        break;
    }
#ifdef CAMU_HAVE_FFMPEG
    case CAMU_FFMPEG_COMPAT: {
        AVPacket *pkt;
        if (packet->opaque) {
            pkt = (AVPacket *)packet->opaque;
        } else {
            pkt = av_packet_alloc();
            nn_packet_read_av_packet(packet, pkt);
            packet->opaque = pkt;
        }
        if (codec->dec) {
            success = push_av_packet(codec, pkt);
            if (!success) {
                success = push_av_packet(codec, pkt);
            }
        } else {
            passthrough_subtitle(codec, pkt);
            success = true;
        }
#ifdef VCR_BUFFER_WHOLE_FILE
        struct camu_codec_stream *stream = codec->handler.stream;
        AVRational time_base = stream->av.stream->time_base;
        pkt->pts += av_rescale_q(stream->duration, AV_TIME_BASE_Q, time_base);
        // @TODO: Shift av_packet_free() to vcr. This leaks right now.
#else
        av_packet_free_side_data(pkt);
        av_packet_free(&pkt);
#endif
        break;
    }
#endif
    default:
        al_assert_and_return(false);
    }

    // Restore packet rindex in case we resue it.
    packet->rindex = rindex;

    if (!success) {
        codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_ERRORED, codec->handler.stream, NULL);
        return false;
    }

    return true;
}

static void codec_client_flush(struct lia_client_handler *handler)
{
    struct lia_codec_client *codec = (struct lia_codec_client *)handler;
    if (codec->dec) codec->dec->flush(codec->dec);
}

static void codec_client_free(struct lia_client_handler **handler)
{
    struct lia_codec_client *codec = (struct lia_codec_client *)*handler;
    if (codec->dec) codec->dec->free(&codec->dec);
    al_free(codec);
    *handler = NULL;
}

struct lia_client_handler *lia_codec_client_create(void)
{
    struct lia_codec_client *codec = al_alloc_object(struct lia_codec_client);
    codec->handler.init = codec_client_init;
    codec->handler.handle_packet = codec_client_handle_packet;
    codec->handler.flush = codec_client_flush;
    codec->handler.free = codec_client_free;
    return (struct lia_client_handler *)codec;
}