1/*
2 * a64 muxer
3 * Copyright (c) 2009 Tobias Bindhammer
4 *
5 * This file is part of Libav.
6 *
7 * Libav is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Libav is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with Libav; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include "libavcodec/avcodec.h"
23#include "libavcodec/a64enc.h"
24#include "libavcodec/bytestream.h"
25#include "avformat.h"
26
27typedef struct A64MuxerContext {
28    int interleaved;
29    AVPacket prev_pkt;
30    int prev_frame_count;
31} A64MuxerContext;
32
33static int a64_write_header(struct AVFormatContext *s)
34{
35    AVCodecContext *avctx = s->streams[0]->codec;
36    A64MuxerContext *c = s->priv_data;
37    uint8_t header[5] = {
38        0x00, //load
39        0x40, //address
40        0x00, //mode
41        0x00, //charset_lifetime (multi only)
42        0x00  //fps in 50/fps;
43    };
44    c->interleaved = 0;
45    switch (avctx->codec->id) {
46    case CODEC_ID_A64_MULTI:
47        header[2] = 0x00;
48        header[3] = AV_RB32(avctx->extradata+0);
49        header[4] = 2;
50        break;
51    case CODEC_ID_A64_MULTI5:
52        header[2] = 0x01;
53        header[3] = AV_RB32(avctx->extradata+0);
54        header[4] = 3;
55        break;
56    default:
57        return AVERROR(EINVAL);
58    }
59    avio_write(s->pb, header, 2);
60    c->prev_pkt.size = 0;
61    c->prev_frame_count = 0;
62    return 0;
63}
64
65static int a64_write_packet(struct AVFormatContext *s, AVPacket *pkt)
66{
67    AVCodecContext *avctx = s->streams[0]->codec;
68    A64MuxerContext *c = s->priv_data;
69    int i, j;
70    int ch_chunksize;
71    int lifetime;
72    int frame_count;
73    int charset_size;
74    int frame_size;
75    int num_frames;
76
77    /* fetch values from extradata */
78    switch (avctx->codec->id) {
79    case CODEC_ID_A64_MULTI:
80    case CODEC_ID_A64_MULTI5:
81        if(c->interleaved) {
82            /* Write interleaved, means we insert chunks of the future charset before each current frame.
83             * Reason: if we load 1 charset + corresponding frames in one block on c64, we need to store
84             * them first and then display frame by frame to keep in sync. Thus we would read and write
85             * the data for colram from/to ram first and waste too much time. If we interleave and send the
86             * charset beforehand, we assemble a new charset chunk by chunk, write current screen data to
87             * screen-ram to be displayed and decode the colram directly to colram-location $d800 during
88             * the overscan, while reading directly from source.
89             * This is the only way so far, to achieve 25fps on c64 */
90            if(avctx->extradata) {
91                /* fetch values from extradata */
92                lifetime     = AV_RB32(avctx->extradata + 0);
93                frame_count  = AV_RB32(avctx->extradata + 4);
94                charset_size = AV_RB32(avctx->extradata + 8);
95                frame_size   = AV_RB32(avctx->extradata + 12);
96
97                /* TODO: sanity checks? */
98            } else {
99                av_log(avctx, AV_LOG_ERROR, "extradata not set\n");
100                return AVERROR(EINVAL);
101            }
102
103            ch_chunksize=charset_size/lifetime;
104            /* TODO: check if charset/size is % lifetime, but maybe check in codec */
105
106            if(pkt->data) num_frames = lifetime;
107            else num_frames = c->prev_frame_count;
108
109            for(i = 0; i < num_frames; i++) {
110                if(pkt->data) {
111                    /* if available, put newest charset chunk into buffer */
112                    avio_write(s->pb, pkt->data + ch_chunksize * i, ch_chunksize);
113                } else {
114                    /* a bit ugly, but is there an alternative to put many zeros? */
115                    for(j = 0; j < ch_chunksize; j++) avio_w8(s->pb, 0);
116                }
117
118                if(c->prev_pkt.data) {
119                    /* put frame (screen + colram) from last packet into buffer */
120                    avio_write(s->pb, c->prev_pkt.data + charset_size + frame_size * i, frame_size);
121                } else {
122                    /* a bit ugly, but is there an alternative to put many zeros? */
123                    for(j = 0; j < frame_size; j++) avio_w8(s->pb, 0);
124                }
125            }
126
127            /* backup current packet for next turn */
128            if(pkt->data) {
129                /* no backup packet yet? create one! */
130                if(!c->prev_pkt.data) av_new_packet(&c->prev_pkt, pkt->size);
131                /* we have a packet and data is big enough, reuse it */
132                if(c->prev_pkt.data && c->prev_pkt.size >= pkt->size) {
133                    memcpy(c->prev_pkt.data, pkt->data, pkt->size);
134                    c->prev_pkt.size = pkt->size;
135                } else {
136                    av_log(avctx, AV_LOG_ERROR, "Too less memory for prev_pkt.\n");
137                    return AVERROR(ENOMEM);
138                }
139            }
140
141            c->prev_frame_count = frame_count;
142            break;
143        }
144        default:
145            /* Write things as is. Nice for self-contained frames from non-multicolor modes or if played
146             * directly from ram and not from a streaming device (rrnet/mmc) */
147            if(pkt) avio_write(s->pb, pkt->data, pkt->size);
148        break;
149    }
150
151    avio_flush(s->pb);
152    return 0;
153}
154
155static int a64_write_trailer(struct AVFormatContext *s)
156{
157    A64MuxerContext *c = s->priv_data;
158    AVPacket pkt = {0};
159    /* need to flush last packet? */
160    if(c->interleaved) a64_write_packet(s, &pkt);
161    /* discard backed up packet */
162    if(c->prev_pkt.data) av_destruct_packet(&c->prev_pkt);
163    return 0;
164}
165
166AVOutputFormat ff_a64_muxer = {
167    .name = "a64",
168    .long_name = NULL_IF_CONFIG_SMALL("a64 - video for Commodore 64"),
169    .extensions = "a64, A64",
170    .priv_data_size = sizeof (A64Context),
171    .video_codec = CODEC_ID_A64_MULTI,
172    .write_header  = a64_write_header,
173    .write_packet  = a64_write_packet,
174    .write_trailer = a64_write_trailer
175};
176