aboutsummaryrefslogtreecommitdiffstats
path: root/libavformat/a64.c
blob: 476db1cf3c35300a822c11a4821c7bfda6bf4c9c (plain) (blame)
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
175
176
/*
 * a64 muxer
 * Copyright (c) 2009 Tobias Bindhammer
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "libavcodec/avcodec.h"
#include "libavcodec/a64enc.h"
#include "libavcodec/bytestream.h"
#include "avformat.h"

typedef struct A64MuxerContext {
    int interleaved;
    AVPacket prev_pkt;
    int prev_frame_count;
} A64MuxerContext;

static int a64_write_header(struct AVFormatContext *s)
{
    AVCodecContext *avctx = s->streams[0]->codec;
    A64MuxerContext *c = s->priv_data;
    uint8_t header[5] = {
        0x00, //load
        0x40, //address
        0x00, //mode
        0x00, //charset_lifetime (multi only)
        0x00  //fps in 50/fps;
    };
    c->interleaved = 0;
    switch (avctx->codec->id) {
    case CODEC_ID_A64_MULTI:
        header[2] = 0x00;
        header[3] = AV_RB32(avctx->extradata+0);
        header[4] = 2;
        break;
    case CODEC_ID_A64_MULTI5:
        header[2] = 0x01;
        header[3] = AV_RB32(avctx->extradata+0);
        header[4] = 3;
        break;
    default:
        return AVERROR(EINVAL);
    }
    avio_write(s->pb, header, 2);
    c->prev_pkt.size = 0;
    c->prev_frame_count = 0;
    return 0;
}

static int a64_write_packet(struct AVFormatContext *s, AVPacket *pkt)
{
    AVCodecContext *avctx = s->streams[0]->codec;
    A64MuxerContext *c = s->priv_data;
    int i, j;
    int ch_chunksize;
    int lifetime;
    int frame_count;
    int charset_size;
    int frame_size;
    int num_frames;

    /* fetch values from extradata */
    switch (avctx->codec->id) {
    case CODEC_ID_A64_MULTI:
    case CODEC_ID_A64_MULTI5:
        if(c->interleaved) {
            /* Write interleaved, means we insert chunks of the future charset before each current frame.
             * Reason: if we load 1 charset + corresponding frames in one block on c64, we need to store
             * them first and then display frame by frame to keep in sync. Thus we would read and write
             * the data for colram from/to ram first and waste too much time. If we interleave and send the
             * charset beforehand, we assemble a new charset chunk by chunk, write current screen data to
             * screen-ram to be displayed and decode the colram directly to colram-location $d800 during
             * the overscan, while reading directly from source.
             * This is the only way so far, to achieve 25fps on c64 */
            if(avctx->extradata) {
                /* fetch values from extradata */
                lifetime     = AV_RB32(avctx->extradata + 0);
                frame_count  = AV_RB32(avctx->extradata + 4);
                charset_size = AV_RB32(avctx->extradata + 8);
                frame_size   = AV_RB32(avctx->extradata + 12);

                /* TODO: sanity checks? */
            } else {
                av_log(avctx, AV_LOG_ERROR, "extradata not set\n");
                return AVERROR(EINVAL);
            }

            ch_chunksize=charset_size/lifetime;
            /* TODO: check if charset/size is % lifetime, but maybe check in codec */

            if(pkt->data) num_frames = lifetime;
            else num_frames = c->prev_frame_count;

            for(i = 0; i < num_frames; i++) {
                if(pkt->data) {
                    /* if available, put newest charset chunk into buffer */
                    avio_write(s->pb, pkt->data + ch_chunksize * i, ch_chunksize);
                } else {
                    /* a bit ugly, but is there an alternative to put many zeros? */
                    for(j = 0; j < ch_chunksize; j++) avio_w8(s->pb, 0);
                }

                if(c->prev_pkt.data) {
                    /* put frame (screen + colram) from last packet into buffer */
                    avio_write(s->pb, c->prev_pkt.data + charset_size + frame_size * i, frame_size);
                } else {
                    /* a bit ugly, but is there an alternative to put many zeros? */
                    for(j = 0; j < frame_size; j++) avio_w8(s->pb, 0);
                }
            }

            /* backup current packet for next turn */
            if(pkt->data) {
                /* no backup packet yet? create one! */
                if(!c->prev_pkt.data) av_new_packet(&c->prev_pkt, pkt->size);
                /* we have a packet and data is big enough, reuse it */
                if(c->prev_pkt.data && c->prev_pkt.size >= pkt->size) {
                    memcpy(c->prev_pkt.data, pkt->data, pkt->size);
                    c->prev_pkt.size = pkt->size;
                } else {
                    av_log(avctx, AV_LOG_ERROR, "Too less memory for prev_pkt.\n");
                    return AVERROR(ENOMEM);
                }
            }

            c->prev_frame_count = frame_count;
            break;
        }
        default:
            /* Write things as is. Nice for self-contained frames from non-multicolor modes or if played
             * directly from ram and not from a streaming device (rrnet/mmc) */
            if(pkt) avio_write(s->pb, pkt->data, pkt->size);
        break;
    }

    avio_flush(s->pb);
    return 0;
}

static int a64_write_trailer(struct AVFormatContext *s)
{
    A64MuxerContext *c = s->priv_data;
    AVPacket pkt = {0};
    /* need to flush last packet? */
    if(c->interleaved) a64_write_packet(s, &pkt);
    /* discard backed up packet */
    if(c->prev_pkt.data) av_destruct_packet(&c->prev_pkt);
    return 0;
}

AVOutputFormat ff_a64_muxer = {
    .name = "a64",
    .long_name = NULL_IF_CONFIG_SMALL("a64 - video for Commodore 64"),
    .mime_type = NULL,
    .extensions = "a64, A64",
    .priv_data_size = sizeof (A64Context),
    .video_codec = CODEC_ID_A64_MULTI,
    a64_write_header,
    a64_write_packet,
    a64_write_trailer
};