/* * Psygnosis YOP demuxer * * Copyright (C) 2010 Mohamed Naufal Basheer <naufal11@gmail.com> * derived from the code by * Copyright (C) 2009 Thomas P. Higdon <thomas.p.higdon@gmail.com> * * This file is part of Libav. * * Libav 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. * * Libav 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 Libav; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "avformat.h" #include "internal.h" typedef struct yop_dec_context { AVPacket video_packet; int odd_frame; int frame_size; int audio_block_length; int palette_size; } YopDecContext; static int yop_probe(AVProbeData *probe_packet) { if (AV_RB16(probe_packet->buf) == AV_RB16("YO") && probe_packet->buf[6] && probe_packet->buf[7] && !(probe_packet->buf[8] & 1) && !(probe_packet->buf[10] & 1)) return AVPROBE_SCORE_MAX * 3 / 4; return 0; } static int yop_read_header(AVFormatContext *s) { YopDecContext *yop = s->priv_data; AVIOContext *pb = s->pb; AVCodecParameters *audio_par, *video_par; AVStream *audio_stream, *video_stream; int frame_rate, ret; audio_stream = avformat_new_stream(s, NULL); video_stream = avformat_new_stream(s, NULL); // Extra data that will be passed to the decoder video_stream->codecpar->extradata = av_mallocz(8 + AV_INPUT_BUFFER_PADDING_SIZE); if (!video_stream->codecpar->extradata) return AVERROR(ENOMEM); video_stream->codecpar->extradata_size = 8; // Audio audio_par = audio_stream->codecpar; audio_par->codec_type = AVMEDIA_TYPE_AUDIO; audio_par->codec_id = AV_CODEC_ID_ADPCM_IMA_APC; audio_par->channels = 1; audio_par->channel_layout = AV_CH_LAYOUT_MONO; audio_par->sample_rate = 22050; // Video video_par = video_stream->codecpar; video_par->codec_type = AVMEDIA_TYPE_VIDEO; video_par->codec_id = AV_CODEC_ID_YOP; avio_skip(pb, 6); frame_rate = avio_r8(pb); yop->frame_size = avio_r8(pb) * 2048; video_par->width = avio_rl16(pb); video_par->height = avio_rl16(pb); video_stream->sample_aspect_ratio = (AVRational){1, 2}; ret = avio_read(pb, video_par->extradata, 8); if (ret < 8) return ret < 0 ? ret : AVERROR_EOF; yop->palette_size = video_par->extradata[0] * 3 + 4; yop->audio_block_length = AV_RL16(video_par->extradata + 6); // 1840 samples per frame, 1 nibble per sample; hence 1840/2 = 920 if (yop->audio_block_length < 920 || yop->audio_block_length + yop->palette_size >= yop->frame_size) { av_log(s, AV_LOG_ERROR, "YOP has invalid header\n"); return AVERROR_INVALIDDATA; } avio_seek(pb, 2048, SEEK_SET); avpriv_set_pts_info(video_stream, 32, 1, frame_rate); return 0; } static int yop_read_packet(AVFormatContext *s, AVPacket *pkt) { YopDecContext *yop = s->priv_data; AVIOContext *pb = s->pb; int ret; int actual_video_data_size = yop->frame_size - yop->audio_block_length - yop->palette_size; yop->video_packet.stream_index = 1; if (yop->video_packet.data) { *pkt = yop->video_packet; yop->video_packet.data = NULL; yop->video_packet.buf = NULL; yop->video_packet.size = 0; pkt->data[0] = yop->odd_frame; pkt->flags |= AV_PKT_FLAG_KEY; yop->odd_frame ^= 1; return pkt->size; } ret = av_new_packet(&yop->video_packet, yop->frame_size - yop->audio_block_length); if (ret < 0) return ret; yop->video_packet.pos = avio_tell(pb); ret = avio_read(pb, yop->video_packet.data, yop->palette_size); if (ret < 0) { goto err_out; }else if (ret < yop->palette_size) { ret = AVERROR_EOF; goto err_out; } ret = av_get_packet(pb, pkt, 920); if (ret < 0) goto err_out; // Set position to the start of the frame pkt->pos = yop->video_packet.pos; avio_skip(pb, yop->audio_block_length - ret); ret = avio_read(pb, yop->video_packet.data + yop->palette_size, actual_video_data_size); if (ret < 0) goto err_out; else if (ret < actual_video_data_size) av_shrink_packet(&yop->video_packet, yop->palette_size + ret); // Arbitrarily return the audio data first return yop->audio_block_length; err_out: av_packet_unref(&yop->video_packet); return ret; } static int yop_read_close(AVFormatContext *s) { YopDecContext *yop = s->priv_data; av_packet_unref(&yop->video_packet); return 0; } static int yop_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { YopDecContext *yop = s->priv_data; int64_t frame_pos, pos_min, pos_max; int frame_count; if (!stream_index) return -1; pos_min = s->internal->data_offset; pos_max = avio_size(s->pb) - yop->frame_size; frame_count = (pos_max - pos_min) / yop->frame_size; timestamp = FFMAX(0, FFMIN(frame_count, timestamp)); frame_pos = timestamp * yop->frame_size + pos_min; if (avio_seek(s->pb, frame_pos, SEEK_SET) < 0) return -1; av_packet_unref(&yop->video_packet); yop->odd_frame = timestamp & 1; return 0; } AVInputFormat ff_yop_demuxer = { .name = "yop", .long_name = NULL_IF_CONFIG_SMALL("Psygnosis YOP"), .priv_data_size = sizeof(YopDecContext), .read_probe = yop_probe, .read_header = yop_read_header, .read_packet = yop_read_packet, .read_close = yop_read_close, .read_seek = yop_read_seek, .extensions = "yop", .flags = AVFMT_GENERIC_INDEX, };