yading@11: /* yading@11: * ARMovie/RPL demuxer yading@11: * Copyright (c) 2007 Christian Ohm, 2008 Eli Friedman yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: #include "libavutil/avstring.h" yading@11: #include "libavutil/dict.h" yading@11: #include "avformat.h" yading@11: #include "internal.h" yading@11: #include yading@11: yading@11: #define RPL_SIGNATURE "ARMovie\x0A" yading@11: #define RPL_SIGNATURE_SIZE 8 yading@11: yading@11: /** 256 is arbitrary, but should be big enough for any reasonable file. */ yading@11: #define RPL_LINE_LENGTH 256 yading@11: yading@11: static int rpl_probe(AVProbeData *p) yading@11: { yading@11: if (memcmp(p->buf, RPL_SIGNATURE, RPL_SIGNATURE_SIZE)) yading@11: return 0; yading@11: yading@11: return AVPROBE_SCORE_MAX; yading@11: } yading@11: yading@11: typedef struct RPLContext { yading@11: // RPL header data yading@11: int32_t frames_per_chunk; yading@11: yading@11: // Stream position data yading@11: uint32_t chunk_number; yading@11: uint32_t chunk_part; yading@11: uint32_t frame_in_part; yading@11: } RPLContext; yading@11: yading@11: static int read_line(AVIOContext * pb, char* line, int bufsize) yading@11: { yading@11: int i; yading@11: for (i = 0; i < bufsize - 1; i++) { yading@11: int b = avio_r8(pb); yading@11: if (b == 0) yading@11: break; yading@11: if (b == '\n') { yading@11: line[i] = '\0'; yading@11: return url_feof(pb) ? -1 : 0; yading@11: } yading@11: line[i] = b; yading@11: } yading@11: line[i] = '\0'; yading@11: return -1; yading@11: } yading@11: yading@11: static int32_t read_int(const char* line, const char** endptr, int* error) yading@11: { yading@11: unsigned long result = 0; yading@11: for (; *line>='0' && *line<='9'; line++) { yading@11: if (result > (0x7FFFFFFF - 9) / 10) yading@11: *error = -1; yading@11: result = 10 * result + *line - '0'; yading@11: } yading@11: *endptr = line; yading@11: return result; yading@11: } yading@11: yading@11: static int32_t read_line_and_int(AVIOContext * pb, int* error) yading@11: { yading@11: char line[RPL_LINE_LENGTH]; yading@11: const char *endptr; yading@11: *error |= read_line(pb, line, sizeof(line)); yading@11: return read_int(line, &endptr, error); yading@11: } yading@11: yading@11: /** Parsing for fps, which can be a fraction. Unfortunately, yading@11: * the spec for the header leaves out a lot of details, yading@11: * so this is mostly guessing. yading@11: */ yading@11: static AVRational read_fps(const char* line, int* error) yading@11: { yading@11: int64_t num, den = 1; yading@11: AVRational result; yading@11: num = read_int(line, &line, error); yading@11: if (*line == '.') yading@11: line++; yading@11: for (; *line>='0' && *line<='9'; line++) { yading@11: // Truncate any numerator too large to fit into an int64_t yading@11: if (num > (INT64_MAX - 9) / 10 || den > INT64_MAX / 10) yading@11: break; yading@11: num = 10 * num + *line - '0'; yading@11: den *= 10; yading@11: } yading@11: if (!num) yading@11: *error = -1; yading@11: av_reduce(&result.num, &result.den, num, den, 0x7FFFFFFF); yading@11: return result; yading@11: } yading@11: yading@11: static int rpl_read_header(AVFormatContext *s) yading@11: { yading@11: AVIOContext *pb = s->pb; yading@11: RPLContext *rpl = s->priv_data; yading@11: AVStream *vst = NULL, *ast = NULL; yading@11: int total_audio_size; yading@11: int error = 0; yading@11: yading@11: uint32_t i; yading@11: yading@11: int32_t audio_format, chunk_catalog_offset, number_of_chunks; yading@11: AVRational fps; yading@11: yading@11: char line[RPL_LINE_LENGTH]; yading@11: yading@11: // The header for RPL/ARMovie files is 21 lines of text yading@11: // containing the various header fields. The fields are always yading@11: // in the same order, and other text besides the first yading@11: // number usually isn't important. yading@11: // (The spec says that there exists some significance yading@11: // for the text in a few cases; samples needed.) yading@11: error |= read_line(pb, line, sizeof(line)); // ARMovie yading@11: error |= read_line(pb, line, sizeof(line)); // movie name yading@11: av_dict_set(&s->metadata, "title" , line, 0); yading@11: error |= read_line(pb, line, sizeof(line)); // date/copyright yading@11: av_dict_set(&s->metadata, "copyright", line, 0); yading@11: error |= read_line(pb, line, sizeof(line)); // author and other yading@11: av_dict_set(&s->metadata, "author" , line, 0); yading@11: yading@11: // video headers yading@11: vst = avformat_new_stream(s, NULL); yading@11: if (!vst) yading@11: return AVERROR(ENOMEM); yading@11: vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; yading@11: vst->codec->codec_tag = read_line_and_int(pb, &error); // video format yading@11: vst->codec->width = read_line_and_int(pb, &error); // video width yading@11: vst->codec->height = read_line_and_int(pb, &error); // video height yading@11: vst->codec->bits_per_coded_sample = read_line_and_int(pb, &error); // video bits per sample yading@11: error |= read_line(pb, line, sizeof(line)); // video frames per second yading@11: fps = read_fps(line, &error); yading@11: avpriv_set_pts_info(vst, 32, fps.den, fps.num); yading@11: yading@11: // Figure out the video codec yading@11: switch (vst->codec->codec_tag) { yading@11: #if 0 yading@11: case 122: yading@11: vst->codec->codec_id = AV_CODEC_ID_ESCAPE122; yading@11: break; yading@11: #endif yading@11: case 124: yading@11: vst->codec->codec_id = AV_CODEC_ID_ESCAPE124; yading@11: // The header is wrong here, at least sometimes yading@11: vst->codec->bits_per_coded_sample = 16; yading@11: break; yading@11: case 130: yading@11: vst->codec->codec_id = AV_CODEC_ID_ESCAPE130; yading@11: break; yading@11: default: yading@11: av_log(s, AV_LOG_WARNING, yading@11: "RPL video format %i not supported yet!\n", yading@11: vst->codec->codec_tag); yading@11: vst->codec->codec_id = AV_CODEC_ID_NONE; yading@11: } yading@11: yading@11: // Audio headers yading@11: yading@11: // ARMovie supports multiple audio tracks; I don't have any yading@11: // samples, though. This code will ignore additional tracks. yading@11: audio_format = read_line_and_int(pb, &error); // audio format ID yading@11: if (audio_format) { yading@11: ast = avformat_new_stream(s, NULL); yading@11: if (!ast) yading@11: return AVERROR(ENOMEM); yading@11: ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; yading@11: ast->codec->codec_tag = audio_format; yading@11: ast->codec->sample_rate = read_line_and_int(pb, &error); // audio bitrate yading@11: ast->codec->channels = read_line_and_int(pb, &error); // number of audio channels yading@11: ast->codec->bits_per_coded_sample = read_line_and_int(pb, &error); // audio bits per sample yading@11: // At least one sample uses 0 for ADPCM, which is really 4 bits yading@11: // per sample. yading@11: if (ast->codec->bits_per_coded_sample == 0) yading@11: ast->codec->bits_per_coded_sample = 4; yading@11: yading@11: ast->codec->bit_rate = ast->codec->sample_rate * yading@11: ast->codec->bits_per_coded_sample * yading@11: ast->codec->channels; yading@11: yading@11: ast->codec->codec_id = AV_CODEC_ID_NONE; yading@11: switch (audio_format) { yading@11: case 1: yading@11: if (ast->codec->bits_per_coded_sample == 16) { yading@11: // 16-bit audio is always signed yading@11: ast->codec->codec_id = AV_CODEC_ID_PCM_S16LE; yading@11: break; yading@11: } yading@11: // There are some other formats listed as legal per the spec; yading@11: // samples needed. yading@11: break; yading@11: case 101: yading@11: if (ast->codec->bits_per_coded_sample == 8) { yading@11: // The samples with this kind of audio that I have yading@11: // are all unsigned. yading@11: ast->codec->codec_id = AV_CODEC_ID_PCM_U8; yading@11: break; yading@11: } else if (ast->codec->bits_per_coded_sample == 4) { yading@11: ast->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_EA_SEAD; yading@11: break; yading@11: } yading@11: break; yading@11: } yading@11: if (ast->codec->codec_id == AV_CODEC_ID_NONE) { yading@11: av_log(s, AV_LOG_WARNING, yading@11: "RPL audio format %i not supported yet!\n", yading@11: audio_format); yading@11: } yading@11: avpriv_set_pts_info(ast, 32, 1, ast->codec->bit_rate); yading@11: } else { yading@11: for (i = 0; i < 3; i++) yading@11: error |= read_line(pb, line, sizeof(line)); yading@11: } yading@11: yading@11: rpl->frames_per_chunk = read_line_and_int(pb, &error); // video frames per chunk yading@11: if (rpl->frames_per_chunk > 1 && vst->codec->codec_tag != 124) yading@11: av_log(s, AV_LOG_WARNING, yading@11: "Don't know how to split frames for video format %i. " yading@11: "Video stream will be broken!\n", vst->codec->codec_tag); yading@11: yading@11: number_of_chunks = read_line_and_int(pb, &error); // number of chunks in the file yading@11: // The number in the header is actually the index of the last chunk. yading@11: number_of_chunks++; yading@11: yading@11: error |= read_line(pb, line, sizeof(line)); // "even" chunk size in bytes yading@11: error |= read_line(pb, line, sizeof(line)); // "odd" chunk size in bytes yading@11: chunk_catalog_offset = // offset of the "chunk catalog" yading@11: read_line_and_int(pb, &error); // (file index) yading@11: error |= read_line(pb, line, sizeof(line)); // offset to "helpful" sprite yading@11: error |= read_line(pb, line, sizeof(line)); // size of "helpful" sprite yading@11: error |= read_line(pb, line, sizeof(line)); // offset to key frame list yading@11: yading@11: // Read the index yading@11: avio_seek(pb, chunk_catalog_offset, SEEK_SET); yading@11: total_audio_size = 0; yading@11: for (i = 0; !error && i < number_of_chunks; i++) { yading@11: int64_t offset, video_size, audio_size; yading@11: error |= read_line(pb, line, sizeof(line)); yading@11: if (3 != sscanf(line, "%"SCNd64" , %"SCNd64" ; %"SCNd64, yading@11: &offset, &video_size, &audio_size)) yading@11: error = -1; yading@11: av_add_index_entry(vst, offset, i * rpl->frames_per_chunk, yading@11: video_size, rpl->frames_per_chunk, 0); yading@11: if (ast) yading@11: av_add_index_entry(ast, offset + video_size, total_audio_size, yading@11: audio_size, audio_size * 8, 0); yading@11: total_audio_size += audio_size * 8; yading@11: } yading@11: yading@11: if (error) return AVERROR(EIO); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt) yading@11: { yading@11: RPLContext *rpl = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: AVStream* stream; yading@11: AVIndexEntry* index_entry; yading@11: uint32_t ret; yading@11: yading@11: if (rpl->chunk_part == s->nb_streams) { yading@11: rpl->chunk_number++; yading@11: rpl->chunk_part = 0; yading@11: } yading@11: yading@11: stream = s->streams[rpl->chunk_part]; yading@11: yading@11: if (rpl->chunk_number >= stream->nb_index_entries) yading@11: return AVERROR_EOF; yading@11: yading@11: index_entry = &stream->index_entries[rpl->chunk_number]; yading@11: yading@11: if (rpl->frame_in_part == 0) yading@11: if (avio_seek(pb, index_entry->pos, SEEK_SET) < 0) yading@11: return AVERROR(EIO); yading@11: yading@11: if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO && yading@11: stream->codec->codec_tag == 124) { yading@11: // We have to split Escape 124 frames because there are yading@11: // multiple frames per chunk in Escape 124 samples. yading@11: uint32_t frame_size; yading@11: yading@11: avio_skip(pb, 4); /* flags */ yading@11: frame_size = avio_rl32(pb); yading@11: if (avio_seek(pb, -8, SEEK_CUR) < 0) yading@11: return AVERROR(EIO); yading@11: yading@11: ret = av_get_packet(pb, pkt, frame_size); yading@11: if (ret != frame_size) { yading@11: av_free_packet(pkt); yading@11: return AVERROR(EIO); yading@11: } yading@11: pkt->duration = 1; yading@11: pkt->pts = index_entry->timestamp + rpl->frame_in_part; yading@11: pkt->stream_index = rpl->chunk_part; yading@11: yading@11: rpl->frame_in_part++; yading@11: if (rpl->frame_in_part == rpl->frames_per_chunk) { yading@11: rpl->frame_in_part = 0; yading@11: rpl->chunk_part++; yading@11: } yading@11: } else { yading@11: ret = av_get_packet(pb, pkt, index_entry->size); yading@11: if (ret != index_entry->size) { yading@11: av_free_packet(pkt); yading@11: return AVERROR(EIO); yading@11: } yading@11: yading@11: if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) { yading@11: // frames_per_chunk should always be one here; the header yading@11: // parsing will warn if it isn't. yading@11: pkt->duration = rpl->frames_per_chunk; yading@11: } else { yading@11: // All the audio codecs supported in this container yading@11: // (at least so far) are constant-bitrate. yading@11: pkt->duration = ret * 8; yading@11: } yading@11: pkt->pts = index_entry->timestamp; yading@11: pkt->stream_index = rpl->chunk_part; yading@11: rpl->chunk_part++; yading@11: } yading@11: yading@11: // None of the Escape formats have keyframes, and the ADPCM yading@11: // format used doesn't have keyframes. yading@11: if (rpl->chunk_number == 0 && rpl->frame_in_part == 0) yading@11: pkt->flags |= AV_PKT_FLAG_KEY; yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: AVInputFormat ff_rpl_demuxer = { yading@11: .name = "rpl", yading@11: .long_name = NULL_IF_CONFIG_SMALL("RPL / ARMovie"), yading@11: .priv_data_size = sizeof(RPLContext), yading@11: .read_probe = rpl_probe, yading@11: .read_header = rpl_read_header, yading@11: .read_packet = rpl_read_packet, yading@11: };