diff options
author | Fabrice Bellard <fabrice@bellard.org> | 2002-11-25 19:07:40 +0000 |
---|---|---|
committer | Fabrice Bellard <fabrice@bellard.org> | 2002-11-25 19:07:40 +0000 |
commit | abac6175916c57f59ddb51ffe41bfd1d9851fe38 (patch) | |
tree | 8fc5b70a143f0f74468c353afbf043d7e104cdc0 /libavformat | |
parent | 57fc25764261abe71b6cf7571eca182f8acf5795 (diff) | |
download | ffmpeg-abac6175916c57f59ddb51ffe41bfd1d9851fe38.tar.gz |
renamed libav to libavformat
Originally committed as revision 1276 to svn://svn.ffmpeg.org/ffmpeg/trunk
Diffstat (limited to 'libavformat')
47 files changed, 18354 insertions, 0 deletions
diff --git a/libavformat/.cvsignore b/libavformat/.cvsignore new file mode 100644 index 0000000000..0cc425cc33 --- /dev/null +++ b/libavformat/.cvsignore @@ -0,0 +1,6 @@ +config.h +config.mak +*ffmpeg +ffserver +Makefile.* +.depend diff --git a/libavformat/Makefile b/libavformat/Makefile new file mode 100644 index 0000000000..1051aa6543 --- /dev/null +++ b/libavformat/Makefile @@ -0,0 +1,72 @@ +# +# libavformat Makefile +# (c) 2000, 2001, 2002 Fabrice Bellard +# +include ../config.mak + +VPATH=$(SRC_PATH)/libav + +CFLAGS= $(OPTFLAGS) -Wall -g -I.. -I$(SRC_PATH) -I$(SRC_PATH)/libavcodec -DHAVE_AV_CONFIG_H -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_GNU_SOURCE + +OBJS= utils.o cutils.o allformats.o + +# mux and demuxes +OBJS+=mpeg.o mpegts.o ffm.o crc.o img.o raw.o rm.o asf.o \ + avienc.o avidec.o wav.o swf.o au.o gif.o mov.o jpeg.o dv.o framehook.o +# file I/O +OBJS+= avio.o aviobuf.o file.o + +ifeq ($(BUILD_STRPTIME),yes) +OBJS+= strptime.o +endif + +ifeq ($(CONFIG_VIDEO4LINUX),yes) +OBJS+= grab.o +endif + +ifeq ($(CONFIG_AUDIO_OSS),yes) +OBJS+= audio.o +endif + +ifeq ($(CONFIG_AUDIO_BEOS),yes) +OBJS+= beosaudio.o +endif + +ifeq ($(CONFIG_NETWORK),yes) +OBJS+= udp.o tcp.o http.o rtsp.o rtp.o rtpproto.o +# BeOS network stuff +ifeq ($(CONFIG_BEOS_NETSERVER),yes) +OBJS+= barpainet.o +endif +endif + +ifeq ($(CONFIG_VORBIS),yes) +OBJS+= ogg.o +endif + +LIB= libavformat.a + +all: $(LIB) + +$(LIB): $(OBJS) + rm -f $@ + $(AR) rc $@ $(OBJS) + $(RANLIB) $@ + +installlib: all + install -m 644 $(LIB) $(prefix)/lib + mkdir -p $(prefix)/include/ffmpeg + install -m 644 $(SRC_PATH)/libav/avformat.h $(SRC_PATH)/libav/avio.h \ + $(SRC_PATH)/libav/rtp.h $(SRC_PATH)/libav/rtsp.h \ + $(SRC_PATH)/libav/rtspcodes.h \ + $(prefix)/include/ffmpeg + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +# BeOS: remove -Wall to get rid of all the "multibyte constant" warnings +%.o: %.cpp + g++ $(subst -Wall,,$(CFLAGS)) -c -o $@ $< + +clean: + rm -f *.o *~ *.a diff --git a/libavformat/allformats.c b/libavformat/allformats.c new file mode 100644 index 0000000000..8a65638e88 --- /dev/null +++ b/libavformat/allformats.c @@ -0,0 +1,74 @@ +/* + * Register all the formats and protocols + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* If you do not call this function, then you can select exactly which + formats you want to support */ + +/** + * Initialize libavcodec and register all the codecs and formats. + */ +void av_register_all(void) +{ + avcodec_init(); + avcodec_register_all(); + + mpegps_init(); + mpegts_init(); + crc_init(); + img_init(); + raw_init(); + rm_init(); + asf_init(); + avienc_init(); + avidec_init(); + wav_init(); + swf_init(); + au_init(); + gif_init(); + mov_init(); + jpeg_init(); + dv_init(); + +#ifdef CONFIG_VORBIS + ogg_init(); +#endif + +#ifndef CONFIG_WIN32 + ffm_init(); +#endif +#ifdef CONFIG_VIDEO4LINUX + video_grab_init(); +#endif +#if defined(CONFIG_AUDIO_OSS) || defined(CONFIG_AUDIO_BEOS) + audio_init(); +#endif + + /* file protocols */ + register_protocol(&file_protocol); + register_protocol(&pipe_protocol); +#ifdef CONFIG_NETWORK + rtsp_init(); + rtp_init(); + register_protocol(&udp_protocol); + register_protocol(&rtp_protocol); + register_protocol(&tcp_protocol); + register_protocol(&http_protocol); +#endif +} diff --git a/libavformat/asf.c b/libavformat/asf.c new file mode 100644 index 0000000000..2f1ce12dc3 --- /dev/null +++ b/libavformat/asf.c @@ -0,0 +1,1256 @@ +/* + * ASF compatible encoder and decoder. + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include "avi.h" +#include "mpegaudio.h" + +#define PACKET_SIZE 3200 +#define PACKET_HEADER_SIZE 12 +#define FRAME_HEADER_SIZE 17 + +typedef struct { + int num; + int seq; + /* use for reading */ + AVPacket pkt; + int frag_offset; + int timestamp; + INT64 duration; + + int ds_span; /* descrambling */ + int ds_packet_size; + int ds_chunk_size; + int ds_data_size; + int ds_silence_data; + +} ASFStream; + +typedef struct { + UINT32 v1; + UINT16 v2; + UINT16 v3; + UINT8 v4[8]; +} GUID; + +typedef struct __attribute__((packed)) { + GUID guid; // generated by client computer + uint64_t file_size; // in bytes + // invalid if broadcasting + uint64_t create_time; // time of creation, in 100-nanosecond units since 1.1.1601 + // invalid if broadcasting + uint64_t packets_count; // how many packets are there in the file + // invalid if broadcasting + uint64_t play_time; // play time, in 100-nanosecond units + // invalid if broadcasting + uint64_t send_time; // time to send file, in 100-nanosecond units + // invalid if broadcasting (could be ignored) + uint32_t preroll; // timestamp of the first packet, in milliseconds + // if nonzero - substract from time + uint32_t ignore; // preroll is 64bit - but let's just ignore it + uint32_t flags; // 0x01 - broadcast + // 0x02 - seekable + // rest is reserved should be 0 + uint32_t min_pktsize; // size of a data packet + // invalid if broadcasting + uint32_t max_pktsize; // shall be the same as for min_pktsize + // invalid if broadcasting + uint32_t max_bitrate; // bandwith of stream in bps + // should be the sum of bitrates of the + // individual media streams +} ASFMainHeader; + + +typedef struct { + int seqno; + int packet_size; + int is_streamed; + int asfid2avid[128]; /* conversion table from asf ID 2 AVStream ID */ + ASFStream streams[128]; /* it's max number and it's not that big */ + /* non streamed additonnal info */ + INT64 nb_packets; + INT64 duration; /* in 100ns units */ + /* packet filling */ + int packet_size_left; + int packet_timestamp_start; + int packet_timestamp_end; + int packet_nb_frames; + UINT8 packet_buf[PACKET_SIZE]; + ByteIOContext pb; + /* only for reading */ + uint64_t data_offset; /* begining of the first data packet */ + + ASFMainHeader hdr; + + int packet_flags; + int packet_property; + int packet_timestamp; + int packet_segsizetype; + int packet_segments; + int packet_seq; + int packet_replic_size; + int packet_key_frame; + int packet_padsize; + int packet_frag_offset; + int packet_frag_size; + int packet_frag_timestamp; + int packet_multi_size; + int packet_obj_size; + int packet_time_delta; + int packet_time_start; + + int stream_index; + ASFStream* asf_st; /* currently decoded stream */ +} ASFContext; + +static const GUID asf_header = { + 0x75B22630, 0x668E, 0x11CF, { 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }, +}; + +static const GUID file_header = { + 0x8CABDCA1, 0xA947, 0x11CF, { 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, +}; + +static const GUID stream_header = { + 0xB7DC0791, 0xA9B7, 0x11CF, { 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, +}; + +static const GUID audio_stream = { + 0xF8699E40, 0x5B4D, 0x11CF, { 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B }, +}; + +static const GUID audio_conceal_none = { + // 0x49f1a440, 0x4ece, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }, + // New value lifted from avifile + 0x20fb5700, 0x5b55, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }, +}; + +static const GUID video_stream = { + 0xBC19EFC0, 0x5B4D, 0x11CF, { 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B }, +}; + +static const GUID video_conceal_none = { + 0x20FB5700, 0x5B55, 0x11CF, { 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B }, +}; + + +static const GUID comment_header = { + 0x75b22633, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }, +}; + +static const GUID codec_comment_header = { + 0x86D15240, 0x311D, 0x11D0, { 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }, +}; +static const GUID codec_comment1_header = { + 0x86d15241, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }, +}; + +static const GUID data_header = { + 0x75b22636, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }, +}; + +static const GUID index_guid = { + 0x33000890, 0xe5b1, 0x11cf, { 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb }, +}; + +static const GUID head1_guid = { + 0x5fbf03b5, 0xa92e, 0x11cf, { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }, +}; + +static const GUID head2_guid = { + 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }, +}; + +/* I am not a number !!! This GUID is the one found on the PC used to + generate the stream */ +static const GUID my_guid = { + 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +static void put_guid(ByteIOContext *s, const GUID *g) +{ + int i; + + put_le32(s, g->v1); + put_le16(s, g->v2); + put_le16(s, g->v3); + for(i=0;i<8;i++) + put_byte(s, g->v4[i]); +} + +static void put_str16(ByteIOContext *s, const char *tag) +{ + int c; + + put_le16(s,strlen(tag) + 1); + for(;;) { + c = (UINT8)*tag++; + put_le16(s, c); + if (c == '\0') + break; + } +} + +static void put_str16_nolen(ByteIOContext *s, const char *tag) +{ + int c; + + for(;;) { + c = (UINT8)*tag++; + put_le16(s, c); + if (c == '\0') + break; + } +} + +static INT64 put_header(ByteIOContext *pb, const GUID *g) +{ + INT64 pos; + + pos = url_ftell(pb); + put_guid(pb, g); + put_le64(pb, 24); + return pos; +} + +/* update header size */ +static void end_header(ByteIOContext *pb, INT64 pos) +{ + INT64 pos1; + + pos1 = url_ftell(pb); + url_fseek(pb, pos + 16, SEEK_SET); + put_le64(pb, pos1 - pos); + url_fseek(pb, pos1, SEEK_SET); +} + +/* write an asf chunk (only used in streaming case) */ +static void put_chunk(AVFormatContext *s, int type, int payload_length, int flags) +{ + ASFContext *asf = s->priv_data; + ByteIOContext *pb = &s->pb; + int length; + + length = payload_length + 8; + put_le16(pb, type); + put_le16(pb, length); + put_le32(pb, asf->seqno); + put_le16(pb, flags); /* unknown bytes */ + put_le16(pb, length); + asf->seqno++; +} + +/* convert from unix to windows time */ +static INT64 unix_to_file_time(int ti) +{ + INT64 t; + + t = ti * INT64_C(10000000); + t += INT64_C(116444736000000000); + return t; +} + +/* write the header (used two times if non streamed) */ +static int asf_write_header1(AVFormatContext *s, INT64 file_size, INT64 data_chunk_size) +{ + ASFContext *asf = s->priv_data; + ByteIOContext *pb = &s->pb; + int header_size, n, extra_size, extra_size2, wav_extra_size, file_time; + int has_title; + AVCodecContext *enc; + INT64 header_offset, cur_pos, hpos; + int bit_rate; + + has_title = (s->title[0] || s->author[0] || s->copyright[0] || s->comment[0]); + + bit_rate = 0; + for(n=0;n<s->nb_streams;n++) { + enc = &s->streams[n]->codec; + + bit_rate += enc->bit_rate; + } + + if (asf->is_streamed) { + put_chunk(s, 0x4824, 0, 0xc00); /* start of stream (length will be patched later) */ + } + + put_guid(pb, &asf_header); + put_le64(pb, -1); /* header length, will be patched after */ + put_le32(pb, 3 + has_title + s->nb_streams); /* number of chunks in header */ + put_byte(pb, 1); /* ??? */ + put_byte(pb, 2); /* ??? */ + + /* file header */ + header_offset = url_ftell(pb); + hpos = put_header(pb, &file_header); + put_guid(pb, &my_guid); + put_le64(pb, file_size); + file_time = 0; + put_le64(pb, unix_to_file_time(file_time)); + put_le64(pb, asf->nb_packets); /* number of packets */ + put_le64(pb, asf->duration); /* end time stamp (in 100ns units) */ + put_le64(pb, asf->duration); /* duration (in 100ns units) */ + put_le32(pb, 0); /* start time stamp */ + put_le32(pb, 0); /* ??? */ + put_le32(pb, asf->is_streamed ? 1 : 0); /* ??? */ + put_le32(pb, asf->packet_size); /* packet size */ + put_le32(pb, asf->packet_size); /* packet size */ + put_le32(pb, bit_rate); /* Nominal data rate in bps */ + end_header(pb, hpos); + + /* unknown headers */ + hpos = put_header(pb, &head1_guid); + put_guid(pb, &head2_guid); + put_le32(pb, 6); + put_le16(pb, 0); + end_header(pb, hpos); + + /* title and other infos */ + if (has_title) { + hpos = put_header(pb, &comment_header); + put_le16(pb, 2 * (strlen(s->title) + 1)); + put_le16(pb, 2 * (strlen(s->author) + 1)); + put_le16(pb, 2 * (strlen(s->copyright) + 1)); + put_le16(pb, 2 * (strlen(s->comment) + 1)); + put_le16(pb, 0); + put_str16_nolen(pb, s->title); + put_str16_nolen(pb, s->author); + put_str16_nolen(pb, s->copyright); + put_str16_nolen(pb, s->comment); + end_header(pb, hpos); + } + + /* stream headers */ + for(n=0;n<s->nb_streams;n++) { + INT64 es_pos; + // ASFStream *stream = &asf->streams[n]; + + enc = &s->streams[n]->codec; + asf->streams[n].num = n + 1; + asf->streams[n].seq = 0; + + switch(enc->codec_type) { + case CODEC_TYPE_AUDIO: + wav_extra_size = 0; + extra_size = 18 + wav_extra_size; + extra_size2 = 0; + break; + default: + case CODEC_TYPE_VIDEO: + wav_extra_size = 0; + extra_size = 0x33; + extra_size2 = 0; + break; + } + + hpos = put_header(pb, &stream_header); + if (enc->codec_type == CODEC_TYPE_AUDIO) { + put_guid(pb, &audio_stream); + put_guid(pb, &audio_conceal_none); + } else { + put_guid(pb, &video_stream); + put_guid(pb, &video_conceal_none); + } + put_le64(pb, 0); /* ??? */ + es_pos = url_ftell(pb); + put_le32(pb, extra_size); /* wav header len */ + put_le32(pb, extra_size2); /* additional data len */ + put_le16(pb, n + 1); /* stream number */ + put_le32(pb, 0); /* ??? */ + + if (enc->codec_type == CODEC_TYPE_AUDIO) { + /* WAVEFORMATEX header */ + int wavsize = put_wav_header(pb, enc); + + if (wavsize < 0) + return -1; + if (wavsize != extra_size) { + cur_pos = url_ftell(pb); + url_fseek(pb, es_pos, SEEK_SET); + put_le32(pb, wavsize); /* wav header len */ + url_fseek(pb, cur_pos, SEEK_SET); + } + } else { + put_le32(pb, enc->width); + put_le32(pb, enc->height); + put_byte(pb, 2); /* ??? */ + put_le16(pb, 40); /* size */ + + /* BITMAPINFOHEADER header */ + put_bmp_header(pb, enc, codec_bmp_tags, 1); + } + end_header(pb, hpos); + } + + /* media comments */ + + hpos = put_header(pb, &codec_comment_header); + put_guid(pb, &codec_comment1_header); + put_le32(pb, s->nb_streams); + for(n=0;n<s->nb_streams;n++) { + AVCodec *p; + + enc = &s->streams[n]->codec; + p = avcodec_find_encoder(enc->codec_id); + + put_le16(pb, asf->streams[n].num); + put_str16(pb, p ? p->name : enc->codec_name); + put_le16(pb, 0); /* no parameters */ + /* id */ + if (enc->codec_type == CODEC_TYPE_AUDIO) { + put_le16(pb, 2); + put_le16(pb, codec_get_tag(codec_wav_tags, enc->codec_id)); + } else { + put_le16(pb, 4); + put_le32(pb, codec_get_tag(codec_bmp_tags, enc->codec_id)); + } + } + end_header(pb, hpos); + + /* patch the header size fields */ + + cur_pos = url_ftell(pb); + header_size = cur_pos - header_offset; + if (asf->is_streamed) { + header_size += 8 + 30 + 50; + + url_fseek(pb, header_offset - 10 - 30, SEEK_SET); + put_le16(pb, header_size); + url_fseek(pb, header_offset - 2 - 30, SEEK_SET); + put_le16(pb, header_size); + + header_size -= 8 + 30 + 50; + } + header_size += 24 + 6; + url_fseek(pb, header_offset - 14, SEEK_SET); + put_le64(pb, header_size); + url_fseek(pb, cur_pos, SEEK_SET); + + /* movie chunk, followed by packets of packet_size */ + asf->data_offset = cur_pos; + put_guid(pb, &data_header); + put_le64(pb, data_chunk_size); + put_guid(pb, &my_guid); + put_le64(pb, asf->nb_packets); /* nb packets */ + put_byte(pb, 1); /* ??? */ + put_byte(pb, 1); /* ??? */ + return 0; +} + +static int asf_write_header(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + + av_set_pts_info(s, 32, 1, 1000); /* 32 bit pts in ms */ + + asf->packet_size = PACKET_SIZE; + asf->nb_packets = 0; + + if (asf_write_header1(s, 0, 50) < 0) { + //av_free(asf); + return -1; + } + + put_flush_packet(&s->pb); + + asf->packet_nb_frames = 0; + asf->packet_timestamp_start = -1; + asf->packet_timestamp_end = -1; + asf->packet_size_left = asf->packet_size - PACKET_HEADER_SIZE; + init_put_byte(&asf->pb, asf->packet_buf, asf->packet_size, 1, + NULL, NULL, NULL, NULL); + + return 0; +} + +static int asf_write_stream_header(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + + asf->is_streamed = 1; + + return asf_write_header(s); +} + +/* write a fixed size packet */ +static int put_packet(AVFormatContext *s, + unsigned int timestamp, unsigned int duration, + int nb_frames, int padsize) +{ + ASFContext *asf = s->priv_data; + ByteIOContext *pb = &s->pb; + int flags; + + if (asf->is_streamed) { + put_chunk(s, 0x4424, asf->packet_size, 0); + } + + put_byte(pb, 0x82); + put_le16(pb, 0); + + flags = 0x01; /* nb segments present */ + if (padsize > 0) { + if (padsize < 256) + flags |= 0x08; + else + flags |= 0x10; + } + put_byte(pb, flags); /* flags */ + put_byte(pb, 0x5d); + if (flags & 0x10) + put_le16(pb, padsize - 2); + if (flags & 0x08) + put_byte(pb, padsize - 1); + put_le32(pb, timestamp); + put_le16(pb, duration); + put_byte(pb, nb_frames | 0x80); + + return PACKET_HEADER_SIZE + ((flags & 0x18) >> 3); +} + +static void flush_packet(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + int hdr_size, ptr; + + hdr_size = put_packet(s, asf->packet_timestamp_start, + asf->packet_timestamp_end - asf->packet_timestamp_start, + asf->packet_nb_frames, asf->packet_size_left); + + /* Clear out the padding bytes */ + ptr = asf->packet_size - hdr_size - asf->packet_size_left; + memset(asf->packet_buf + ptr, 0, asf->packet_size_left); + + put_buffer(&s->pb, asf->packet_buf, asf->packet_size - hdr_size); + + put_flush_packet(&s->pb); + asf->nb_packets++; + asf->packet_nb_frames = 0; + asf->packet_timestamp_start = -1; + asf->packet_timestamp_end = -1; + asf->packet_size_left = asf->packet_size - PACKET_HEADER_SIZE; + init_put_byte(&asf->pb, asf->packet_buf, asf->packet_size, 1, + NULL, NULL, NULL, NULL); +} + +static void put_frame_header(AVFormatContext *s, ASFStream *stream, int timestamp, + int payload_size, int frag_offset, int frag_len) +{ + ASFContext *asf = s->priv_data; + ByteIOContext *pb = &asf->pb; + int val; + + val = stream->num; + if (s->streams[val - 1]->codec.key_frame /* && frag_offset == 0 */) + val |= 0x80; + put_byte(pb, val); + put_byte(pb, stream->seq); + put_le32(pb, frag_offset); /* fragment offset */ + put_byte(pb, 0x08); /* flags */ + put_le32(pb, payload_size); + put_le32(pb, timestamp); + put_le16(pb, frag_len); +} + + +/* Output a frame. We suppose that payload_size <= PACKET_SIZE. + + It is there that you understand that the ASF format is really + crap. They have misread the MPEG Systems spec ! + */ +static void put_frame(AVFormatContext *s, ASFStream *stream, int timestamp, + UINT8 *buf, int payload_size) +{ + ASFContext *asf = s->priv_data; + int frag_pos, frag_len, frag_len1; + + frag_pos = 0; + while (frag_pos < payload_size) { + frag_len = payload_size - frag_pos; + frag_len1 = asf->packet_size_left - FRAME_HEADER_SIZE; + if (frag_len1 > 0) { + if (frag_len > frag_len1) + frag_len = frag_len1; + put_frame_header(s, stream, timestamp+1, payload_size, frag_pos, frag_len); + put_buffer(&asf->pb, buf, frag_len); + asf->packet_size_left -= (frag_len + FRAME_HEADER_SIZE); + asf->packet_timestamp_end = timestamp; + if (asf->packet_timestamp_start == -1) + asf->packet_timestamp_start = timestamp; + asf->packet_nb_frames++; + } else { + frag_len = 0; + } + frag_pos += frag_len; + buf += frag_len; + /* output the frame if filled */ + if (asf->packet_size_left <= FRAME_HEADER_SIZE) + flush_packet(s); + } + stream->seq++; +} + + +static int asf_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int timestamp) +{ + ASFContext *asf = s->priv_data; + ASFStream *stream; + INT64 duration; + AVCodecContext *codec; + + codec = &s->streams[stream_index]->codec; + stream = &asf->streams[stream_index]; + + if (codec->codec_type == CODEC_TYPE_AUDIO) { + duration = (codec->frame_number * codec->frame_size * INT64_C(10000000)) / + codec->sample_rate; + } else { + duration = codec->frame_number * + ((INT64_C(10000000) * FRAME_RATE_BASE) / codec->frame_rate); + } + if (duration > asf->duration) + asf->duration = duration; + + put_frame(s, stream, timestamp, buf, size); + return 0; +} + +static int asf_write_trailer(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + INT64 file_size; + + /* flush the current packet */ + if (asf->pb.buf_ptr > asf->pb.buffer) + flush_packet(s); + + if (asf->is_streamed) { + put_chunk(s, 0x4524, 0, 0); /* end of stream */ + } else { + /* rewrite an updated header */ + file_size = url_ftell(&s->pb); + url_fseek(&s->pb, 0, SEEK_SET); + asf_write_header1(s, file_size, file_size - asf->data_offset); + } + + put_flush_packet(&s->pb); + return 0; +} + +/**********************************/ +/* decoding */ + +//#define DEBUG + +#ifdef DEBUG +static void print_guid(const GUID *g) +{ + int i; + printf("0x%08x, 0x%04x, 0x%04x, {", g->v1, g->v2, g->v3); + for(i=0;i<8;i++) + printf(" 0x%02x,", g->v4[i]); + printf("}\n"); +} +#endif + +static void get_guid(ByteIOContext *s, GUID *g) +{ + int i; + + g->v1 = get_le32(s); + g->v2 = get_le16(s); + g->v3 = get_le16(s); + for(i=0;i<8;i++) + g->v4[i] = get_byte(s); +} + +#if 0 +static void get_str16(ByteIOContext *pb, char *buf, int buf_size) +{ + int len, c; + char *q; + + len = get_le16(pb); + q = buf; + while (len > 0) { + c = get_le16(pb); + if ((q - buf) < buf_size - 1) + *q++ = c; + len--; + } + *q = '\0'; +} +#endif + +static void get_str16_nolen(ByteIOContext *pb, int len, char *buf, int buf_size) +{ + int c; + char *q; + + q = buf; + while (len > 0) { + c = get_le16(pb); + if ((q - buf) < buf_size - 1) + *q++ = c; + len-=2; + } + *q = '\0'; +} + +static int asf_probe(AVProbeData *pd) +{ + GUID g; + const unsigned char *p; + int i; + + /* check file header */ + if (pd->buf_size <= 32) + return 0; + p = pd->buf; + g.v1 = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + p += 4; + g.v2 = p[0] | (p[1] << 8); + p += 2; + g.v3 = p[0] | (p[1] << 8); + p += 2; + for(i=0;i<8;i++) + g.v4[i] = *p++; + + if (!memcmp(&g, &asf_header, sizeof(GUID))) + return AVPROBE_SCORE_MAX; + else + return 0; +} + +static int asf_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + ASFContext *asf = s->priv_data; + GUID g; + ByteIOContext *pb = &s->pb; + AVStream *st; + ASFStream *asf_st; + int size, i; + INT64 gsize; + + av_set_pts_info(s, 32, 1, 1000); /* 32 bit pts in ms */ + + get_guid(pb, &g); + if (memcmp(&g, &asf_header, sizeof(GUID))) + goto fail; + get_le64(pb); + get_le32(pb); + get_byte(pb); + get_byte(pb); + memset(&asf->asfid2avid, -1, sizeof(asf->asfid2avid)); + for(;;) { + get_guid(pb, &g); + gsize = get_le64(pb); +#ifdef DEBUG + printf("%08Lx: ", url_ftell(pb) - 24); + print_guid(&g); + printf(" size=0x%Lx\n", gsize); +#endif + if (gsize < 24) + goto fail; + if (!memcmp(&g, &file_header, sizeof(GUID))) { + get_guid(pb, &asf->hdr.guid); + asf->hdr.file_size = get_le64(pb); + asf->hdr.create_time = get_le64(pb); + asf->hdr.packets_count = get_le64(pb); + asf->hdr.play_time = get_le64(pb); + asf->hdr.send_time = get_le64(pb); + asf->hdr.preroll = get_le32(pb); + asf->hdr.ignore = get_le32(pb); + asf->hdr.flags = get_le32(pb); + asf->hdr.min_pktsize = get_le32(pb); + asf->hdr.max_pktsize = get_le32(pb); + asf->hdr.max_bitrate = get_le32(pb); + asf->packet_size = asf->hdr.max_pktsize; + asf->nb_packets = asf->hdr.packets_count; + } else if (!memcmp(&g, &stream_header, sizeof(GUID))) { + int type, total_size; + unsigned int tag1; + INT64 pos1, pos2; + + pos1 = url_ftell(pb); + + st = av_mallocz(sizeof(AVStream)); + if (!st) + goto fail; + s->streams[s->nb_streams] = st; + asf_st = av_mallocz(sizeof(ASFStream)); + if (!asf_st) + goto fail; + st->priv_data = asf_st; + st->time_length = (asf->hdr.send_time - asf->hdr.preroll) / 10; // us + get_guid(pb, &g); + if (!memcmp(&g, &audio_stream, sizeof(GUID))) { + type = CODEC_TYPE_AUDIO; + } else if (!memcmp(&g, &video_stream, sizeof(GUID))) { + type = CODEC_TYPE_VIDEO; + } else { + goto fail; + } + get_guid(pb, &g); + total_size = get_le64(pb); + get_le32(pb); + get_le32(pb); + st->id = get_le16(pb) & 0x7f; /* stream id */ + // mapping of asf ID to AV stream ID; + asf->asfid2avid[st->id] = s->nb_streams++; + + get_le32(pb); + st->codec.codec_type = type; + st->codec.frame_rate = 15 * s->pts_den / s->pts_num; // 15 fps default + if (type == CODEC_TYPE_AUDIO) { + get_wav_header(pb, &st->codec, 1); + /* We have to init the frame size at some point .... */ + pos2 = url_ftell(pb); + if (gsize > (pos2 + 8 - pos1 + 24)) { + asf_st->ds_span = get_byte(pb); + asf_st->ds_packet_size = get_le16(pb); + asf_st->ds_chunk_size = get_le16(pb); + asf_st->ds_data_size = get_le16(pb); + asf_st->ds_silence_data = get_byte(pb); + } + //printf("Descrambling: ps:%d cs:%d ds:%d s:%d sd:%d\n", + // asf_st->ds_packet_size, asf_st->ds_chunk_size, + // asf_st->ds_data_size, asf_st->ds_span, asf_st->ds_silence_data); + if (asf_st->ds_span > 1) { + if (!asf_st->ds_chunk_size + || (asf_st->ds_packet_size/asf_st->ds_chunk_size <= 1)) + asf_st->ds_span = 0; // disable descrambling + } + switch (st->codec.codec_id) { + case CODEC_ID_MP3LAME: + st->codec.frame_size = MPA_FRAME_SIZE; + break; + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_U16BE: + case CODEC_ID_PCM_S8: + case CODEC_ID_PCM_U8: + case CODEC_ID_PCM_ALAW: + case CODEC_ID_PCM_MULAW: + st->codec.frame_size = 1; + break; + default: + /* This is probably wrong, but it prevents a crash later */ + st->codec.frame_size = 1; + break; + } + } else { + get_le32(pb); + get_le32(pb); + get_byte(pb); + size = get_le16(pb); /* size */ + get_le32(pb); /* size */ + st->codec.width = get_le32(pb); + st->codec.height = get_le32(pb); + /* not available for asf */ + get_le16(pb); /* panes */ + get_le16(pb); /* depth */ + tag1 = get_le32(pb); + url_fskip(pb, 20); + if (size > 40) { + st->codec.extradata_size = size - 40; + st->codec.extradata = av_mallocz(st->codec.extradata_size); + get_buffer(pb, st->codec.extradata, st->codec.extradata_size); + } + st->codec.codec_tag = st->codec.fourcc = tag1; + st->codec.codec_id = codec_get_id(codec_bmp_tags, tag1); + } + pos2 = url_ftell(pb); + url_fskip(pb, gsize - (pos2 - pos1 + 24)); + } else if (!memcmp(&g, &data_header, sizeof(GUID))) { + break; + } else if (!memcmp(&g, &comment_header, sizeof(GUID))) { + int len1, len2, len3, len4, len5; + + len1 = get_le16(pb); + len2 = get_le16(pb); + len3 = get_le16(pb); + len4 = get_le16(pb); + len5 = get_le16(pb); + get_str16_nolen(pb, len1, s->title, sizeof(s->title)); + get_str16_nolen(pb, len2, s->author, sizeof(s->author)); + get_str16_nolen(pb, len3, s->copyright, sizeof(s->copyright)); + get_str16_nolen(pb, len4, s->comment, sizeof(s->comment)); + url_fskip(pb, len5); +#if 0 + } else if (!memcmp(&g, &head1_guid, sizeof(GUID))) { + int v1, v2; + get_guid(pb, &g); + v1 = get_le32(pb); + v2 = get_le16(pb); + } else if (!memcmp(&g, &codec_comment_header, sizeof(GUID))) { + int len, v1, n, num; + char str[256], *q; + char tag[16]; + + get_guid(pb, &g); + print_guid(&g); + + n = get_le32(pb); + for(i=0;i<n;i++) { + num = get_le16(pb); /* stream number */ + get_str16(pb, str, sizeof(str)); + get_str16(pb, str, sizeof(str)); + len = get_le16(pb); + q = tag; + while (len > 0) { + v1 = get_byte(pb); + if ((q - tag) < sizeof(tag) - 1) + *q++ = v1; + len--; + } + *q = '\0'; + } +#endif + } else if (url_feof(pb)) { + goto fail; + } else { + url_fseek(pb, gsize - 24, SEEK_CUR); + } + } + get_guid(pb, &g); + get_le64(pb); + get_byte(pb); + get_byte(pb); + if (url_feof(pb)) + goto fail; + asf->data_offset = url_ftell(pb); + asf->packet_size_left = 0; + + return 0; + + fail: + for(i=0;i<s->nb_streams;i++) { + AVStream *st = s->streams[i]; + if (st) { + av_free(st->priv_data); + av_free(st->codec.extradata); + } + av_free(st); + } + return -1; +} + +#define DO_2BITS(bits, var, defval) \ + switch (bits & 3) \ + { \ + case 3: var = get_le32(pb); rsize += 4; break; \ + case 2: var = get_le16(pb); rsize += 2; break; \ + case 1: var = get_byte(pb); rsize++; break; \ + default: var = defval; break; \ + } + +static int asf_get_packet(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + ByteIOContext *pb = &s->pb; + uint32_t packet_length, padsize; + int rsize = 11; + int c = get_byte(pb); + if (c != 0x82) { + if (!url_feof(pb)) + printf("ff asf bad header %x at:%Ld\n", c, url_ftell(pb)); + return -EIO; + } + if ((c & 0x0f) == 2) { // always true for now + if (get_le16(pb) != 0) { + if (!url_feof(pb)) + printf("ff asf bad non zero\n"); + return -EIO; + } + } + + asf->packet_flags = get_byte(pb); + asf->packet_property = get_byte(pb); + + DO_2BITS(asf->packet_flags >> 5, packet_length, asf->packet_size); + DO_2BITS(asf->packet_flags >> 1, padsize, 0); // sequence ignored + DO_2BITS(asf->packet_flags >> 3, padsize, 0); // padding length + + asf->packet_timestamp = get_le32(pb); + get_le16(pb); /* duration */ + // rsize has at least 11 bytes which have to be present + + if (asf->packet_flags & 0x01) { + asf->packet_segsizetype = get_byte(pb); rsize++; + asf->packet_segments = asf->packet_segsizetype & 0x3f; + } else { + asf->packet_segments = 1; + asf->packet_segsizetype = 0x80; + } + asf->packet_size_left = packet_length - padsize - rsize; + if (packet_length < asf->hdr.min_pktsize) + padsize += asf->hdr.min_pktsize - packet_length; + asf->packet_padsize = padsize; +#ifdef DEBUG + printf("packet: size=%d padsize=%d left=%d\n", asf->packet_size, asf->packet_padsize, asf->packet_size_left); +#endif + return 0; +} + +static int asf_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + ASFContext *asf = s->priv_data; + ASFStream *asf_st = 0; + ByteIOContext *pb = &s->pb; + //static int pc = 0; + for (;;) { + int rsize = 0; + if (asf->packet_size_left < FRAME_HEADER_SIZE + || asf->packet_segments < 1) { + //asf->packet_size_left <= asf->packet_padsize) { + int ret = asf->packet_size_left + asf->packet_padsize; + //printf("PacketLeftSize:%d Pad:%d Pos:%Ld\n", asf->packet_size_left, asf->packet_padsize, url_ftell(pb)); + /* fail safe */ + url_fskip(pb, ret); + ret = asf_get_packet(s); + //printf("READ ASF PACKET %d r:%d c:%d\n", ret, asf->packet_size_left, pc++); + if (ret < 0 || url_feof(pb)) + return -EIO; + asf->packet_time_start = 0; + continue; + } + if (asf->packet_time_start == 0) { + /* read frame header */ + int num = get_byte(pb); + asf->packet_segments--; + rsize++; + asf->packet_key_frame = (num & 0x80) >> 7; + asf->stream_index = asf->asfid2avid[num & 0x7f]; + // sequence should be ignored! + DO_2BITS(asf->packet_property >> 4, asf->packet_seq, 0); + DO_2BITS(asf->packet_property >> 2, asf->packet_frag_offset, 0); + DO_2BITS(asf->packet_property, asf->packet_replic_size, 0); + + if (asf->packet_replic_size > 1) { + // it should be always at least 8 bytes - FIXME validate + asf->packet_obj_size = get_le32(pb); + asf->packet_frag_timestamp = get_le32(pb); // timestamp + if (asf->packet_replic_size > 8) + url_fskip(pb, asf->packet_replic_size - 8); + rsize += asf->packet_replic_size; // FIXME - check validity + } else { + // multipacket - frag_offset is begining timestamp + asf->packet_time_start = asf->packet_frag_offset; + asf->packet_frag_offset = 0; + asf->packet_frag_timestamp = asf->packet_timestamp; + + if (asf->packet_replic_size == 1) { + asf->packet_time_delta = get_byte(pb); + rsize++; + } + } + if (asf->packet_flags & 0x01) { + DO_2BITS(asf->packet_segsizetype >> 6, asf->packet_frag_size, 0); // 0 is illegal +#undef DO_2BITS + //printf("Fragsize %d\n", asf->packet_frag_size); + } else { + asf->packet_frag_size = asf->packet_size_left - rsize; + //printf("Using rest %d %d %d\n", asf->packet_frag_size, asf->packet_size_left, rsize); + } + if (asf->packet_replic_size == 1) { + asf->packet_multi_size = asf->packet_frag_size; + if (asf->packet_multi_size > asf->packet_size_left) { + asf->packet_segments = 0; + continue; + } + } + asf->packet_size_left -= rsize; + //printf("___objsize____ %d %d rs:%d\n", asf->packet_obj_size, asf->packet_frag_offset, rsize); + + if (asf->stream_index < 0) { + asf->packet_time_start = 0; + /* unhandled packet (should not happen) */ + url_fskip(pb, asf->packet_frag_size); + asf->packet_size_left -= asf->packet_frag_size; + printf("ff asf skip %d %d\n", asf->packet_frag_size, num & 0x7f); + continue; + } + asf->asf_st = s->streams[asf->stream_index]->priv_data; + } + asf_st = asf->asf_st; + + if ((asf->packet_frag_offset != asf_st->frag_offset + || (asf->packet_frag_offset + && asf->packet_seq != asf_st->seq)) // seq should be ignored + ) { + /* cannot continue current packet: free it */ + // FIXME better check if packet was already allocated + printf("ff asf parser skips: %d - %d o:%d - %d %d %d fl:%d\n", + asf_st->pkt.size, + asf->packet_obj_size, + asf->packet_frag_offset, asf_st->frag_offset, + asf->packet_seq, asf_st->seq, asf->packet_frag_size); + if (asf_st->pkt.size) + av_free_packet(&asf_st->pkt); + asf_st->frag_offset = 0; + if (asf->packet_frag_offset != 0) { + url_fskip(pb, asf->packet_frag_size); + printf("ff asf parser skiping %db\n", asf->packet_frag_size); + asf->packet_size_left -= asf->packet_frag_size; + continue; + } + } + if (asf->packet_replic_size == 1) { + // frag_offset is here used as the begining timestamp + asf->packet_frag_timestamp = asf->packet_time_start; + asf->packet_time_start += asf->packet_time_delta; + asf->packet_obj_size = asf->packet_frag_size = get_byte(pb); + asf->packet_size_left--; + asf->packet_multi_size--; + if (asf->packet_multi_size < asf->packet_obj_size) + { + asf->packet_time_start = 0; + url_fskip(pb, asf->packet_multi_size); + asf->packet_size_left -= asf->packet_multi_size; + continue; + } + asf->packet_multi_size -= asf->packet_obj_size; + //printf("COMPRESS size %d %d %d ms:%d\n", asf->packet_obj_size, asf->packet_frag_timestamp, asf->packet_size_left, asf->packet_multi_size); + } + if (asf_st->frag_offset == 0) { + /* new packet */ + av_new_packet(&asf_st->pkt, asf->packet_obj_size); + asf_st->seq = asf->packet_seq; + asf_st->pkt.pts = asf->packet_frag_timestamp - asf->hdr.preroll; + asf_st->pkt.stream_index = asf->stream_index; + if (asf->packet_key_frame) + asf_st->pkt.flags |= PKT_FLAG_KEY; + } + + /* read data */ + //printf("READ PACKET s:%d os:%d o:%d,%d l:%d DATA:%p\n", + // asf->packet_size, asf_st->pkt.size, asf->packet_frag_offset, + // asf_st->frag_offset, asf->packet_frag_size, asf_st->pkt.data); + asf->packet_size_left -= asf->packet_frag_size; + if (asf->packet_size_left < 0) + continue; + get_buffer(pb, asf_st->pkt.data + asf->packet_frag_offset, + asf->packet_frag_size); + asf_st->frag_offset += asf->packet_frag_size; + /* test if whole packet is read */ + if (asf_st->frag_offset == asf_st->pkt.size) { + /* return packet */ + if (asf_st->ds_span > 1) { + /* packet descrambling */ + char* newdata = av_malloc(asf_st->pkt.size); + if (newdata) { + int offset = 0; + while (offset < asf_st->pkt.size) { + int off = offset / asf_st->ds_chunk_size; + int row = off / asf_st->ds_span; + int col = off % asf_st->ds_span; + int idx = row + col * asf_st->ds_packet_size / asf_st->ds_chunk_size; + //printf("off:%d row:%d col:%d idx:%d\n", off, row, col, idx); + memcpy(newdata + offset, + asf_st->pkt.data + idx * asf_st->ds_chunk_size, + asf_st->ds_chunk_size); + offset += asf_st->ds_chunk_size; + } + av_free(asf_st->pkt.data); + asf_st->pkt.data = newdata; + } + } + asf_st->frag_offset = 0; + memcpy(pkt, &asf_st->pkt, sizeof(AVPacket)); + //printf("packet %d %d\n", asf_st->pkt.size, asf->packet_frag_size); + asf_st->pkt.size = 0; + asf_st->pkt.data = 0; + break; // packet completed + } + } + return 0; +} + +static int asf_read_close(AVFormatContext *s) +{ + int i; + + for(i=0;i<s->nb_streams;i++) { + AVStream *st = s->streams[i]; + av_free(st->priv_data); + av_free(st->codec.extradata); + } + return 0; +} + +static int asf_read_seek(AVFormatContext *s, int64_t pts) +{ + printf("SEEK TO %Ld", pts); + return -1; +} + +static AVInputFormat asf_iformat = { + "asf", + "asf format", + sizeof(ASFContext), + asf_probe, + asf_read_header, + asf_read_packet, + asf_read_close, + asf_read_seek, +}; + +static AVOutputFormat asf_oformat = { + "asf", + "asf format", + "application/octet-stream", + "asf,wmv", + sizeof(ASFContext), +#ifdef CONFIG_MP3LAME + CODEC_ID_MP3LAME, +#else + CODEC_ID_MP2, +#endif + CODEC_ID_MSMPEG4V3, + asf_write_header, + asf_write_packet, + asf_write_trailer, +}; + +static AVOutputFormat asf_stream_oformat = { + "asf_stream", + "asf format", + "application/octet-stream", + "asf,wmv", + sizeof(ASFContext), +#ifdef CONFIG_MP3LAME + CODEC_ID_MP3LAME, +#else + CODEC_ID_MP2, +#endif + CODEC_ID_MSMPEG4V3, + asf_write_stream_header, + asf_write_packet, + asf_write_trailer, +}; + +int asf_init(void) +{ + av_register_input_format(&asf_iformat); + av_register_output_format(&asf_oformat); + av_register_output_format(&asf_stream_oformat); + return 0; +} diff --git a/libavformat/au.c b/libavformat/au.c new file mode 100644 index 0000000000..8c3d62a595 --- /dev/null +++ b/libavformat/au.c @@ -0,0 +1,214 @@ +/* + * AU encoder and decoder + * Copyright (c) 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * First version by Francois Revol revol@free.fr + * + * Reference documents: + * http://www.opengroup.org/public/pubs/external/auformat.html + * http://www.goice.co.jp/member/mo/formats/au.html + */ + +#include "avformat.h" +#include "avi.h" + +/* if we don't know the size in advance */ +#define AU_UNKOWN_SIZE ((UINT32)(~0)) + +/* The ffmpeg codecs we support, and the IDs they have in the file */ +static const CodecTag codec_au_tags[] = { + { CODEC_ID_PCM_MULAW, 1 }, + { CODEC_ID_PCM_S16BE, 3 }, + { CODEC_ID_PCM_ALAW, 27 }, + { 0, 0 }, +}; + +/* AUDIO_FILE header */ +static int put_au_header(ByteIOContext *pb, AVCodecContext *enc) +{ + int tag; + + tag = codec_get_tag(codec_au_tags, enc->codec_id); + if (tag == 0) + return -1; + put_tag(pb, ".snd"); /* magic number */ + put_be32(pb, 24); /* header size */ + put_be32(pb, AU_UNKOWN_SIZE); /* data size */ + put_be32(pb, (UINT32)tag); /* codec ID */ + put_be32(pb, enc->sample_rate); + put_be32(pb, (UINT32)enc->channels); + return 0; +} + +static int au_write_header(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + + s->priv_data = NULL; + + /* format header */ + if (put_au_header(pb, &s->streams[0]->codec) < 0) { + return -1; + } + + put_flush_packet(pb); + + return 0; +} + +static int au_write_packet(AVFormatContext *s, int stream_index_ptr, + UINT8 *buf, int size, int force_pts) +{ + ByteIOContext *pb = &s->pb; + put_buffer(pb, buf, size); + return 0; +} + +static int au_write_trailer(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + offset_t file_size; + + if (!url_is_streamed(&s->pb)) { + + /* update file size */ + file_size = url_ftell(pb); + url_fseek(pb, 8, SEEK_SET); + put_be32(pb, (UINT32)(file_size - 24)); + url_fseek(pb, file_size, SEEK_SET); + + put_flush_packet(pb); + } + + return 0; +} + +static int au_probe(AVProbeData *p) +{ + /* check file header */ + if (p->buf_size <= 24) + return 0; + if (p->buf[0] == '.' && p->buf[1] == 's' && + p->buf[2] == 'n' && p->buf[3] == 'd') + return AVPROBE_SCORE_MAX; + else + return 0; +} + +/* au input */ +static int au_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + int size; + unsigned int tag; + ByteIOContext *pb = &s->pb; + unsigned int id, codec, channels, rate; + AVStream *st; + + /* check ".snd" header */ + tag = get_le32(pb); + if (tag != MKTAG('.', 's', 'n', 'd')) + return -1; + size = get_be32(pb); /* header size */ + get_be32(pb); /* data size */ + + id = get_be32(pb); + rate = get_be32(pb); + channels = get_be32(pb); + + codec = codec_get_id(codec_au_tags, id); + + if (size >= 24) { + /* skip unused data */ + url_fseek(pb, size - 24, SEEK_CUR); + } + + /* now we are ready: build format streams */ + st = av_malloc(sizeof(AVStream)); + if (!st) + return -1; + s->nb_streams = 1; + s->streams[0] = st; + + st->id = 0; + + st->codec.codec_type = CODEC_TYPE_AUDIO; + st->codec.codec_tag = id; + st->codec.codec_id = codec; + st->codec.channels = channels; + st->codec.sample_rate = rate; + return 0; +} + +#define MAX_SIZE 4096 + +static int au_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + int ret; + + if (url_feof(&s->pb)) + return -EIO; + if (av_new_packet(pkt, MAX_SIZE)) + return -EIO; + pkt->stream_index = 0; + + ret = get_buffer(&s->pb, pkt->data, pkt->size); + if (ret < 0) + av_free_packet(pkt); + /* note: we need to modify the packet size here to handle the last + packet */ + pkt->size = ret; + return 0; +} + +static int au_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat au_iformat = { + "au", + "SUN AU Format", + 0, + au_probe, + au_read_header, + au_read_packet, + au_read_close, +}; + +static AVOutputFormat au_oformat = { + "au", + "SUN AU Format", + "audio/basic", + "au", + 0, + CODEC_ID_PCM_S16BE, + CODEC_ID_NONE, + au_write_header, + au_write_packet, + au_write_trailer, +}; + +int au_init(void) +{ + av_register_input_format(&au_iformat); + av_register_output_format(&au_oformat); + return 0; +} diff --git a/libavformat/audio.c b/libavformat/audio.c new file mode 100644 index 0000000000..4fa155c85d --- /dev/null +++ b/libavformat/audio.c @@ -0,0 +1,330 @@ +/* + * Linux audio play and grab interface + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/soundcard.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/time.h> + +const char *audio_device = "/dev/dsp"; + +#define AUDIO_BLOCK_SIZE 4096 + +typedef struct { + int fd; + int sample_rate; + int channels; + int frame_size; /* in bytes ! */ + int codec_id; + int flip_left : 1; + UINT8 buffer[AUDIO_BLOCK_SIZE]; + int buffer_ptr; +} AudioData; + +static int audio_open(AudioData *s, int is_output) +{ + int audio_fd; + int tmp, err; + char *flip = getenv("AUDIO_FLIP_LEFT"); + + /* open linux audio device */ + if (is_output) + audio_fd = open(audio_device, O_WRONLY); + else + audio_fd = open(audio_device, O_RDONLY); + if (audio_fd < 0) { + perror(audio_device); + return -EIO; + } + + if (flip && *flip == '1') { + s->flip_left = 1; + } + + /* non blocking mode */ + if (!is_output) + fcntl(audio_fd, F_SETFL, O_NONBLOCK); + + s->frame_size = AUDIO_BLOCK_SIZE; +#if 0 + tmp = (NB_FRAGMENTS << 16) | FRAGMENT_BITS; + err = ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &tmp); + if (err < 0) { + perror("SNDCTL_DSP_SETFRAGMENT"); + } +#endif + + /* select format : favour native format */ + err = ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &tmp); + +#ifdef WORDS_BIGENDIAN + if (tmp & AFMT_S16_BE) { + tmp = AFMT_S16_BE; + } else if (tmp & AFMT_S16_LE) { + tmp = AFMT_S16_LE; + } else { + tmp = 0; + } +#else + if (tmp & AFMT_S16_LE) { + tmp = AFMT_S16_LE; + } else if (tmp & AFMT_S16_BE) { + tmp = AFMT_S16_BE; + } else { + tmp = 0; + } +#endif + + switch(tmp) { + case AFMT_S16_LE: + s->codec_id = CODEC_ID_PCM_S16LE; + break; + case AFMT_S16_BE: + s->codec_id = CODEC_ID_PCM_S16BE; + break; + default: + fprintf(stderr, "Soundcard does not support 16 bit sample format\n"); + close(audio_fd); + return -EIO; + } + err=ioctl(audio_fd, SNDCTL_DSP_SETFMT, &tmp); + if (err < 0) { + perror("SNDCTL_DSP_SETFMT"); + goto fail; + } + + tmp = (s->channels == 2); + err = ioctl(audio_fd, SNDCTL_DSP_STEREO, &tmp); + if (err < 0) { + perror("SNDCTL_DSP_STEREO"); + goto fail; + } + if (tmp) + s->channels = 2; + + tmp = s->sample_rate; + err = ioctl(audio_fd, SNDCTL_DSP_SPEED, &tmp); + if (err < 0) { + perror("SNDCTL_DSP_SPEED"); + goto fail; + } + s->sample_rate = tmp; /* store real sample rate */ + s->fd = audio_fd; + + return 0; + fail: + close(audio_fd); + return -EIO; +} + +static int audio_close(AudioData *s) +{ + close(s->fd); + return 0; +} + +/* sound output support */ +static int audio_write_header(AVFormatContext *s1) +{ + AudioData *s = s1->priv_data; + AVStream *st; + int ret; + + st = s1->streams[0]; + s->sample_rate = st->codec.sample_rate; + s->channels = st->codec.channels; + ret = audio_open(s, 1); + if (ret < 0) { + return -EIO; + } else { + return 0; + } +} + +static int audio_write_packet(AVFormatContext *s1, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AudioData *s = s1->priv_data; + int len, ret; + + while (size > 0) { + len = AUDIO_BLOCK_SIZE - s->buffer_ptr; + if (len > size) + len = size; + memcpy(s->buffer + s->buffer_ptr, buf, len); + s->buffer_ptr += len; + if (s->buffer_ptr >= AUDIO_BLOCK_SIZE) { + for(;;) { + ret = write(s->fd, s->buffer, AUDIO_BLOCK_SIZE); + if (ret > 0) + break; + if (ret < 0 && (errno != EAGAIN && errno != EINTR)) + return -EIO; + } + s->buffer_ptr = 0; + } + buf += len; + size -= len; + } + return 0; +} + +static int audio_write_trailer(AVFormatContext *s1) +{ + AudioData *s = s1->priv_data; + + audio_close(s); + return 0; +} + +/* grab support */ + +static int audio_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + AudioData *s = s1->priv_data; + AVStream *st; + int ret; + + if (!ap || ap->sample_rate <= 0 || ap->channels <= 0) + return -1; + + st = av_new_stream(s1, 0); + if (!st) { + return -ENOMEM; + } + s->sample_rate = ap->sample_rate; + s->channels = ap->channels; + + ret = audio_open(s, 0); + if (ret < 0) { + av_free(st); + return -EIO; + } + + /* take real parameters */ + st->codec.codec_type = CODEC_TYPE_AUDIO; + st->codec.codec_id = s->codec_id; + st->codec.sample_rate = s->sample_rate; + st->codec.channels = s->channels; + + av_set_pts_info(s1, 48, 1, 1000000); /* 48 bits pts in us */ + return 0; +} + +static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + AudioData *s = s1->priv_data; + int ret, bdelay; + int64_t cur_time; + struct audio_buf_info abufi; + + if (av_new_packet(pkt, s->frame_size) < 0) + return -EIO; + for(;;) { + ret = read(s->fd, pkt->data, pkt->size); + if (ret > 0) + break; + if (ret == -1 && (errno == EAGAIN || errno == EINTR)) { + av_free_packet(pkt); + pkt->size = 0; + return 0; + } + if (!(ret == 0 || (ret == -1 && (errno == EAGAIN || errno == EINTR)))) { + av_free_packet(pkt); + return -EIO; + } + } + pkt->size = ret; + + /* compute pts of the start of the packet */ + cur_time = av_gettime(); + bdelay = ret; + if (ioctl(s->fd, SNDCTL_DSP_GETISPACE, &abufi) == 0) { + bdelay += abufi.bytes; + } + /* substract time represented by the number of bytes in the audio fifo */ + cur_time -= (bdelay * 1000000LL) / (s->sample_rate * s->channels); + + /* convert to wanted units */ + pkt->pts = cur_time & ((1LL << 48) - 1); + + if (s->flip_left && s->channels == 2) { + int i; + short *p = (short *) pkt->data; + + for (i = 0; i < ret; i += 4) { + *p = ~*p; + p += 2; + } + } + return 0; +} + +static int audio_read_close(AVFormatContext *s1) +{ + AudioData *s = s1->priv_data; + + audio_close(s); + return 0; +} + +static AVInputFormat audio_in_format = { + "audio_device", + "audio grab and output", + sizeof(AudioData), + NULL, + audio_read_header, + audio_read_packet, + audio_read_close, + .flags = AVFMT_NOFILE, +}; + +static AVOutputFormat audio_out_format = { + "audio_device", + "audio grab and output", + "", + "", + sizeof(AudioData), + /* XXX: we make the assumption that the soundcard accepts this format */ + /* XXX: find better solution with "preinit" method, needed also in + other formats */ +#ifdef WORDS_BIGENDIAN + CODEC_ID_PCM_S16BE, +#else + CODEC_ID_PCM_S16LE, +#endif + CODEC_ID_NONE, + audio_write_header, + audio_write_packet, + audio_write_trailer, + .flags = AVFMT_NOFILE, +}; + +int audio_init(void) +{ + av_register_input_format(&audio_in_format); + av_register_output_format(&audio_out_format); + return 0; +} diff --git a/libavformat/avformat.h b/libavformat/avformat.h new file mode 100644 index 0000000000..0ea10f7dfe --- /dev/null +++ b/libavformat/avformat.h @@ -0,0 +1,351 @@ +#ifndef AVFORMAT_H +#define AVFORMAT_H + +#define LIBAVFORMAT_VERSION_INT 0x000406 +#define LIBAVFORMAT_VERSION "0.4.6" +#define LIBAVFORMAT_BUILD 4602 + +#include "avcodec.h" + +#include "avio.h" + +/* packet functions */ + +#define AV_NOPTS_VALUE 0 + +typedef struct AVPacket { + INT64 pts; /* presentation time stamp in stream units (set av_set_pts_info) */ + UINT8 *data; + int size; + int stream_index; + int flags; + int duration; +#define PKT_FLAG_KEY 0x0001 +} AVPacket; + +int av_new_packet(AVPacket *pkt, int size); +void av_free_packet(AVPacket *pkt); + +/*************************************************/ +/* fractional numbers for exact pts handling */ + +/* the exact value of the fractional number is: 'val + num / den'. num + is assumed to be such as 0 <= num < den */ +typedef struct AVFrac { + INT64 val, num, den; +} AVFrac; + +void av_frac_init(AVFrac *f, INT64 val, INT64 num, INT64 den); +void av_frac_add(AVFrac *f, INT64 incr); +void av_frac_set(AVFrac *f, INT64 val); + +/*************************************************/ +/* input/output formats */ + +struct AVFormatContext; + +/* this structure contains the data a format has to probe a file */ +typedef struct AVProbeData { + char *filename; + unsigned char *buf; + int buf_size; +} AVProbeData; + +#define AVPROBE_SCORE_MAX 100 + +typedef struct AVFormatParameters { + int frame_rate; + int sample_rate; + int channels; + int width; + int height; + enum PixelFormat pix_fmt; +} AVFormatParameters; + +#define AVFMT_NOFILE 0x0001 /* no file should be opened */ +#define AVFMT_NEEDNUMBER 0x0002 /* needs '%d' in filename */ +#define AVFMT_NOHEADER 0x0004 /* signal that no header is present + (streams are added dynamically) */ +#define AVFMT_SHOW_IDS 0x0008 /* show format stream IDs numbers */ +#define AVFMT_RGB24 0x0010 /* force RGB24 output for ppm (hack + - need better api) */ +#define AVFMT_RAWPICTURE 0x0020 /* format wants AVPicture structure for + raw picture data */ + +typedef struct AVOutputFormat { + const char *name; + const char *long_name; + const char *mime_type; + const char *extensions; /* comma separated extensions */ + /* size of private data so that it can be allocated in the wrapper */ + int priv_data_size; + /* output support */ + enum CodecID audio_codec; /* default audio codec */ + enum CodecID video_codec; /* default video codec */ + int (*write_header)(struct AVFormatContext *); + /* XXX: change prototype for 64 bit pts */ + int (*write_packet)(struct AVFormatContext *, + int stream_index, + unsigned char *buf, int size, int force_pts); + int (*write_trailer)(struct AVFormatContext *); + /* can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER */ + int flags; + /* private fields */ + struct AVOutputFormat *next; +} AVOutputFormat; + +typedef struct AVInputFormat { + const char *name; + const char *long_name; + /* size of private data so that it can be allocated in the wrapper */ + int priv_data_size; + /* tell if a given file has a chance of being parsing by this format */ + int (*read_probe)(AVProbeData *); + /* read the format header and initialize the AVFormatContext + structure. Return 0 if OK. 'ap' if non NULL contains + additionnal paramters. Only used in raw format right + now. 'av_new_stream' should be called to create new streams. */ + int (*read_header)(struct AVFormatContext *, + AVFormatParameters *ap); + /* read one packet and put it in 'pkt'. pts and flags are also + set. 'av_new_stream' can be called only if the flag + AVFMT_NOHEADER is used. */ + int (*read_packet)(struct AVFormatContext *, AVPacket *pkt); + /* close the stream. The AVFormatContext and AVStreams are not + freed by this function */ + int (*read_close)(struct AVFormatContext *); + /* seek at or before a given pts (given in microsecond). The pts + origin is defined by the stream */ + int (*read_seek)(struct AVFormatContext *, INT64 pts); + /* can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_NOHEADER */ + int flags; + /* if extensions are defined, then no probe is done. You should + usually not use extension format guessing because it is not + reliable enough */ + const char *extensions; + /* general purpose read only value that the format can use */ + int value; + /* private fields */ + struct AVInputFormat *next; +} AVInputFormat; + +typedef struct AVStream { + int index; /* stream index in AVFormatContext */ + int id; /* format specific stream id */ + AVCodecContext codec; /* codec context */ + int r_frame_rate; /* real frame rate of the stream */ + uint64_t time_length; /* real length of the stream in miliseconds */ + void *priv_data; + /* internal data used in av_find_stream_info() */ + int codec_info_state; + int codec_info_nb_repeat_frames; + int codec_info_nb_real_frames; + /* PTS generation when outputing stream */ + AVFrac pts; + /* ffmpeg.c private use */ + int stream_copy; /* if TRUE, just copy stream */ +} AVStream; + +#define MAX_STREAMS 20 + +/* format I/O context */ +typedef struct AVFormatContext { + /* can only be iformat or oformat, not both at the same time */ + struct AVInputFormat *iformat; + struct AVOutputFormat *oformat; + void *priv_data; + ByteIOContext pb; + int nb_streams; + AVStream *streams[MAX_STREAMS]; + char filename[1024]; /* input or output filename */ + /* stream info */ + char title[512]; + char author[512]; + char copyright[512]; + char comment[512]; + int flags; /* format specific flags */ + /* private data for pts handling (do not modify directly) */ + int pts_wrap_bits; /* number of bits in pts (used for wrapping control) */ + int pts_num, pts_den; /* value to convert to seconds */ + /* This buffer is only needed when packets were already buffered but + not decoded, for example to get the codec parameters in mpeg + streams */ + struct AVPacketList *packet_buffer; +} AVFormatContext; + +typedef struct AVPacketList { + AVPacket pkt; + struct AVPacketList *next; +} AVPacketList; + +extern AVInputFormat *first_iformat; +extern AVOutputFormat *first_oformat; + +/* XXX: use automatic init with either ELF sections or C file parser */ +/* modules */ + +/* mpeg.c */ +int mpegps_init(void); + +/* mpegts.c */ +extern AVInputFormat mpegts_demux; +int mpegts_init(void); + +/* rm.c */ +int rm_init(void); + +/* crc.c */ +int crc_init(void); + +/* img.c */ +int img_init(void); + +/* asf.c */ +int asf_init(void); + +/* avienc.c */ +int avienc_init(void); + +/* avidec.c */ +int avidec_init(void); + +/* swf.c */ +int swf_init(void); + +/* mov.c */ +int mov_init(void); + +/* jpeg.c */ +int jpeg_init(void); + +/* gif.c */ +int gif_init(void); + +/* au.c */ +int au_init(void); + +/* wav.c */ +int wav_init(void); + +/* raw.c */ +int raw_init(void); + +/* ogg.c */ +int ogg_init(void); + +/* dv.c */ +int dv_init(void); + +/* ffm.c */ +int ffm_init(void); + +/* rtsp.c */ +extern AVInputFormat redir_demux; +int redir_open(AVFormatContext **ic_ptr, ByteIOContext *f); + +#include "rtp.h" + +#include "rtsp.h" + +/* utils.c */ +#define MKTAG(a,b,c,d) (a | (b << 8) | (c << 16) | (d << 24)) +#define MKBETAG(a,b,c,d) (d | (c << 8) | (b << 16) | (a << 24)) + +void av_register_input_format(AVInputFormat *format); +void av_register_output_format(AVOutputFormat *format); +AVOutputFormat *guess_stream_format(const char *short_name, + const char *filename, const char *mime_type); +AVOutputFormat *guess_format(const char *short_name, + const char *filename, const char *mime_type); + +void av_hex_dump(UINT8 *buf, int size); + +void av_register_all(void); + +typedef struct FifoBuffer { + UINT8 *buffer; + UINT8 *rptr, *wptr, *end; +} FifoBuffer; + +int fifo_init(FifoBuffer *f, int size); +void fifo_free(FifoBuffer *f); +int fifo_size(FifoBuffer *f, UINT8 *rptr); +int fifo_read(FifoBuffer *f, UINT8 *buf, int buf_size, UINT8 **rptr_ptr); +void fifo_write(FifoBuffer *f, UINT8 *buf, int size, UINT8 **wptr_ptr); + +/* media file input */ +AVInputFormat *av_find_input_format(const char *short_name); +AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened); +int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, + AVInputFormat *fmt, + int buf_size, + AVFormatParameters *ap); + +#define AVERROR_UNKNOWN (-1) /* unknown error */ +#define AVERROR_IO (-2) /* i/o error */ +#define AVERROR_NUMEXPECTED (-3) /* number syntax expected in filename */ +#define AVERROR_INVALIDDATA (-4) /* invalid data found */ +#define AVERROR_NOMEM (-5) /* not enough memory */ +#define AVERROR_NOFMT (-6) /* unknown format */ + +int av_find_stream_info(AVFormatContext *ic); +int av_read_packet(AVFormatContext *s, AVPacket *pkt); +void av_close_input_file(AVFormatContext *s); +AVStream *av_new_stream(AVFormatContext *s, int id); +void av_set_pts_info(AVFormatContext *s, int pts_wrap_bits, + int pts_num, int pts_den); + +/* media file output */ +int av_write_header(AVFormatContext *s); +int av_write_frame(AVFormatContext *s, int stream_index, const uint8_t *buf, + int size); +int av_write_trailer(AVFormatContext *s); + +void dump_format(AVFormatContext *ic, + int index, + const char *url, + int is_output); +int parse_image_size(int *width_ptr, int *height_ptr, const char *str); +INT64 parse_date(const char *datestr, int duration); + +INT64 av_gettime(void); + +/* ffm specific for ffserver */ +#define FFM_PACKET_SIZE 4096 +offset_t ffm_read_write_index(int fd); +void ffm_write_write_index(int fd, offset_t pos); +void ffm_set_write_index(AVFormatContext *s, offset_t pos, offset_t file_size); + +int find_info_tag(char *arg, int arg_size, const char *tag1, const char *info); + +int get_frame_filename(char *buf, int buf_size, + const char *path, int number); +int filename_number_test(const char *filename); + +/* grab specific */ +int video_grab_init(void); +int audio_init(void); + +extern const char *v4l_device; +extern const char *audio_device; + +#ifdef HAVE_AV_CONFIG_H +int strstart(const char *str, const char *val, const char **ptr); +int stristart(const char *str, const char *val, const char **ptr); +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); + +struct in_addr; +int resolve_host(struct in_addr *sin_addr, const char *hostname); + +void url_split(char *proto, int proto_size, + char *hostname, int hostname_size, + int *port_ptr, + char *path, int path_size, + const char *url); + +int match_ext(const char *filename, const char *extensions); + +#endif /* HAVE_AV_CONFIG_H */ + +#endif /* AVFORMAT_H */ diff --git a/libavformat/avi.h b/libavformat/avi.h new file mode 100644 index 0000000000..d1b5eb5789 --- /dev/null +++ b/libavformat/avi.h @@ -0,0 +1,28 @@ + +#define AVIF_HASINDEX 0x00000010 // Index at end of file? +#define AVIF_MUSTUSEINDEX 0x00000020 +#define AVIF_ISINTERLEAVED 0x00000100 +#define AVIF_TRUSTCKTYPE 0x00000800 // Use CKType to find key frames? +#define AVIF_WASCAPTUREFILE 0x00010000 +#define AVIF_COPYRIGHTED 0x00020000 + +offset_t start_tag(ByteIOContext *pb, const char *tag); +void end_tag(ByteIOContext *pb, offset_t start); + +typedef struct CodecTag { + int id; + unsigned int tag; + unsigned int invalid_asf : 1; +} CodecTag; + +void put_bmp_header(ByteIOContext *pb, AVCodecContext *enc, const CodecTag *tags, int for_asf); +int put_wav_header(ByteIOContext *pb, AVCodecContext *enc); +int wav_codec_get_id(unsigned int tag, int bps); +void get_wav_header(ByteIOContext *pb, AVCodecContext *codec, + int has_extra_data); + +extern const CodecTag codec_bmp_tags[]; +extern const CodecTag codec_wav_tags[]; + +unsigned int codec_get_tag(const CodecTag *tags, int id); +int codec_get_id(const CodecTag *tags, unsigned int tag); diff --git a/libavformat/avidec.c b/libavformat/avidec.c new file mode 100644 index 0000000000..9646408cd1 --- /dev/null +++ b/libavformat/avidec.c @@ -0,0 +1,256 @@ +/* + * AVI decoder. + * Copyright (c) 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include "avi.h" + +//#define DEBUG + +typedef struct AVIIndex { + unsigned char tag[4]; + unsigned int flags, pos, len; + struct AVIIndex *next; +} AVIIndex; + +typedef struct { + INT64 movi_end; + offset_t movi_list; + AVIIndex *first, *last; +} AVIContext; + +#ifdef DEBUG +static void print_tag(const char *str, unsigned int tag, int size) +{ + printf("%s: tag=%c%c%c%c size=0x%x\n", + str, tag & 0xff, + (tag >> 8) & 0xff, + (tag >> 16) & 0xff, + (tag >> 24) & 0xff, + size); +} +#endif + +static int avi_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + AVIContext *avi = s->priv_data; + ByteIOContext *pb = &s->pb; + UINT32 tag, tag1; + int codec_type, stream_index, size, frame_period, bit_rate; + int i; + AVStream *st; + + /* check RIFF header */ + tag = get_le32(pb); + + if (tag != MKTAG('R', 'I', 'F', 'F')) + return -1; + get_le32(pb); /* file size */ + tag = get_le32(pb); + if (tag != MKTAG('A', 'V', 'I', ' ')) + return -1; + + /* first list tag */ + stream_index = -1; + codec_type = -1; + frame_period = 0; + for(;;) { + if (url_feof(pb)) + goto fail; + tag = get_le32(pb); + size = get_le32(pb); +#ifdef DEBUG + print_tag("tag", tag, size); +#endif + + switch(tag) { + case MKTAG('L', 'I', 'S', 'T'): + /* ignored, except when start of video packets */ + tag1 = get_le32(pb); +#ifdef DEBUG + print_tag("list", tag1, 0); +#endif + if (tag1 == MKTAG('m', 'o', 'v', 'i')) { + avi->movi_end = url_ftell(pb) + size - 4; +#ifdef DEBUG + printf("movi end=%Lx\n", avi->movi_end); +#endif + goto end_of_header; + } + break; + case MKTAG('a', 'v', 'i', 'h'): + /* avi header */ + /* using frame_period is bad idea */ + frame_period = get_le32(pb); + bit_rate = get_le32(pb) * 8; + url_fskip(pb, 4 * 4); + s->nb_streams = get_le32(pb); + for(i=0;i<s->nb_streams;i++) { + AVStream *st = av_mallocz(sizeof(AVStream)); + if (!st) + goto fail; + s->streams[i] = st; + } + url_fskip(pb, size - 7 * 4); + break; + case MKTAG('s', 't', 'r', 'h'): + /* stream header */ + stream_index++; + tag1 = get_le32(pb); + switch(tag1) { + case MKTAG('v', 'i', 'd', 's'): + codec_type = CODEC_TYPE_VIDEO; + get_le32(pb); /* codec tag */ + get_le32(pb); /* flags */ + get_le16(pb); /* priority */ + get_le16(pb); /* language */ + get_le32(pb); /* XXX: initial frame ? */ + get_le32(pb); /* scale */ + get_le32(pb); /* rate */ + size -= 6 * 4; + break; + case MKTAG('a', 'u', 'd', 's'): + codec_type = CODEC_TYPE_AUDIO; + /* nothing really useful */ + } + url_fskip(pb, size - 4); + break; + case MKTAG('s', 't', 'r', 'f'): + /* stream header */ + if (stream_index >= s->nb_streams) { + url_fskip(pb, size); + } else { + st = s->streams[stream_index]; + switch(codec_type) { + case CODEC_TYPE_VIDEO: + get_le32(pb); /* size */ + st->codec.width = get_le32(pb); + st->codec.height = get_le32(pb); + if (frame_period) + st->codec.frame_rate = (INT64_C(1000000) * FRAME_RATE_BASE) / frame_period; + else + st->codec.frame_rate = 25 * FRAME_RATE_BASE; + get_le16(pb); /* panes */ + get_le16(pb); /* depth */ + tag1 = get_le32(pb); +#ifdef DEBUG + print_tag("video", tag1, 0); +#endif + st->codec.codec_type = CODEC_TYPE_VIDEO; + st->codec.codec_tag = tag1; + st->codec.codec_id = codec_get_id(codec_bmp_tags, tag1); + url_fskip(pb, size - 5 * 4); + break; + case CODEC_TYPE_AUDIO: + get_wav_header(pb, &st->codec, (size >= 18)); + break; + default: + url_fskip(pb, size); + break; + } + } + break; + default: + /* skip tag */ + size += (size & 1); + url_fskip(pb, size); + break; + } + } + end_of_header: + /* check stream number */ + if (stream_index != s->nb_streams - 1) { + fail: + for(i=0;i<s->nb_streams;i++) { + av_freep(&s->streams[i]); + } + return -1; + } + + return 0; +} + +static int avi_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIContext *avi = s->priv_data; + ByteIOContext *pb = &s->pb; + int n, d1, d2, size; + + for(;;) { + if (url_feof(pb) || url_ftell(pb) >= avi->movi_end) + return -1; + d1 = get_byte(pb) - '0'; + d2 = get_byte(pb) - '0'; + if (d1 < 0 || d1 > 9 || d2 < 0 || d2 > 9) + continue; + + n = d1 * 10 + d2; + if (n < 0 || n >= s->nb_streams) + continue; + + d1 = get_byte(pb); + d2 = get_byte(pb); + if ((d1 == 'd' && d2 == 'c') + || (d1 == 'w' && d2 == 'b')) + break; + } + size = get_le32(pb); + av_new_packet(pkt, size); + pkt->stream_index = n; + + get_buffer(pb, pkt->data, pkt->size); + + if (size & 1) + get_byte(pb); + + return 0; +} + +static int avi_read_close(AVFormatContext *s) +{ + return 0; +} + +static int avi_probe(AVProbeData *p) +{ + /* check file header */ + if (p->buf_size <= 32) + return 0; + if (p->buf[0] == 'R' && p->buf[1] == 'I' && + p->buf[2] == 'F' && p->buf[3] == 'F' && + p->buf[8] == 'A' && p->buf[9] == 'V' && + p->buf[10] == 'I' && p->buf[11] == ' ') + return AVPROBE_SCORE_MAX; + else + return 0; +} + +static AVInputFormat avi_iformat = { + "avi", + "avi format", + sizeof(AVIContext), + avi_probe, + avi_read_header, + avi_read_packet, + avi_read_close, +}; + +int avidec_init(void) +{ + av_register_input_format(&avi_iformat); + return 0; +} diff --git a/libavformat/avienc.c b/libavformat/avienc.c new file mode 100644 index 0000000000..b4298b0b39 --- /dev/null +++ b/libavformat/avienc.c @@ -0,0 +1,432 @@ +/* + * AVI encoder. + * Copyright (c) 2000 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include "avi.h" + +/* + * TODO: + * - fill all fields if non streamed (nb_frames for example) + */ + +typedef struct AVIIndex { + unsigned char tag[4]; + unsigned int flags, pos, len; + struct AVIIndex *next; +} AVIIndex; + +typedef struct { + offset_t movi_list, frames_hdr_all, frames_hdr_strm[MAX_STREAMS]; + int audio_strm_length[MAX_STREAMS]; + AVIIndex *first, *last; +} AVIContext; + +offset_t start_tag(ByteIOContext *pb, const char *tag) +{ + put_tag(pb, tag); + put_le32(pb, 0); + return url_ftell(pb); +} + +void end_tag(ByteIOContext *pb, offset_t start) +{ + offset_t pos; + + pos = url_ftell(pb); + url_fseek(pb, start - 4, SEEK_SET); + put_le32(pb, (UINT32)(pos - start)); + url_fseek(pb, pos, SEEK_SET); +} + +/* Note: when encoding, the first matching tag is used, so order is + important if multiple tags possible for a given codec. */ +const CodecTag codec_bmp_tags[] = { + { CODEC_ID_H263, MKTAG('H', '2', '6', '3') }, + { CODEC_ID_H263P, MKTAG('H', '2', '6', '3') }, + { CODEC_ID_H263I, MKTAG('I', '2', '6', '3') }, /* intel h263 */ + { CODEC_ID_MJPEG, MKTAG('M', 'J', 'P', 'G') }, + { CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', 'X'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('d', 'i', 'v', 'x'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('D', 'X', '5', '0'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('X', 'V', 'I', 'D'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('x', 'v', 'i', 'd'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 's'), .invalid_asf = 1 }, + { CODEC_ID_MPEG4, MKTAG('M', 'P', '4', 'S') }, + { CODEC_ID_MPEG4, MKTAG('M', '4', 'S', '2') }, + { CODEC_ID_MPEG4, MKTAG('m', '4', 's', '2') }, + { CODEC_ID_MPEG4, MKTAG(0x04, 0, 0, 0) }, /* some broken avi use this */ + { CODEC_ID_MSMPEG4V3, MKTAG('D', 'I', 'V', '3'), .invalid_asf = 1 }, /* default signature when using MSMPEG4 */ + { CODEC_ID_MSMPEG4V3, MKTAG('d', 'i', 'v', '3'), .invalid_asf = 1 }, + { CODEC_ID_MSMPEG4V3, MKTAG('M', 'P', '4', '3') }, + { CODEC_ID_MSMPEG4V2, MKTAG('M', 'P', '4', '2') }, + { CODEC_ID_MSMPEG4V1, MKTAG('M', 'P', 'G', '4') }, + { CODEC_ID_WMV1, MKTAG('W', 'M', 'V', '1') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 's', 'l') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 's', 'd') }, + { CODEC_ID_DVVIDEO, MKTAG('D', 'V', 'S', 'D') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'h', 'd') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '1') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '2') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('P', 'I', 'M', '1') }, + { CODEC_ID_MJPEG, MKTAG('M', 'J', 'P', 'G') }, + { 0, 0 }, +}; + +unsigned int codec_get_tag(const CodecTag *tags, int id) +{ + while (tags->id != 0) { + if (tags->id == id) + return tags->tag; + tags++; + } + return 0; +} + +static unsigned int codec_get_asf_tag(const CodecTag *tags, int id) +{ + while (tags->id != 0) { + if (!tags->invalid_asf && tags->id == id) + return tags->tag; + tags++; + } + return 0; +} + +int codec_get_id(const CodecTag *tags, unsigned int tag) +{ + while (tags->id != 0) { + if (tags->tag == tag) + return tags->id; + tags++; + } + return 0; +} + +unsigned int codec_get_bmp_tag(int id) +{ + return codec_get_tag(codec_bmp_tags, id); +} + +/* BITMAPINFOHEADER header */ +void put_bmp_header(ByteIOContext *pb, AVCodecContext *enc, const CodecTag *tags, int for_asf) +{ + put_le32(pb, 40); /* size */ + put_le32(pb, enc->width); + put_le32(pb, enc->height); + put_le16(pb, 1); /* planes */ + put_le16(pb, 24); /* depth */ + /* compression type */ + put_le32(pb, for_asf ? codec_get_asf_tag(tags, enc->codec_id) : codec_get_tag(tags, enc->codec_id)); + put_le32(pb, enc->width * enc->height * 3); + put_le32(pb, 0); + put_le32(pb, 0); + put_le32(pb, 0); + put_le32(pb, 0); +} + +static void parse_specific_params(AVCodecContext *stream, int *au_byterate, int *au_ssize, int *au_scale) +{ + switch(stream->codec_id) { + case CODEC_ID_PCM_S16LE: + *au_scale = *au_ssize = 2*stream->channels; + *au_byterate = *au_ssize * stream->sample_rate; + break; + case CODEC_ID_PCM_U8: + case CODEC_ID_PCM_ALAW: + case CODEC_ID_PCM_MULAW: + *au_scale = *au_ssize = stream->channels; + *au_byterate = *au_ssize * stream->sample_rate; + break; + case CODEC_ID_MP2: + *au_ssize = 1; + *au_scale = 1; + *au_byterate = stream->bit_rate / 8; + case CODEC_ID_MP3LAME: + *au_ssize = 1; + *au_scale = 1; + *au_byterate = stream->bit_rate / 8; + default: + *au_ssize = 1; + *au_scale = 1; + *au_byterate = stream->bit_rate / 8; + break; + } +} + +static int avi_write_header(AVFormatContext *s) +{ + AVIContext *avi = s->priv_data; + ByteIOContext *pb = &s->pb; + int bitrate, n, i, nb_frames, au_byterate, au_ssize, au_scale; + AVCodecContext *stream, *video_enc; + offset_t list1, list2, strh, strf; + + put_tag(pb, "RIFF"); + put_le32(pb, 0); /* file length */ + put_tag(pb, "AVI "); + + /* header list */ + list1 = start_tag(pb, "LIST"); + put_tag(pb, "hdrl"); + + /* avi header */ + put_tag(pb, "avih"); + put_le32(pb, 14 * 4); + bitrate = 0; + + video_enc = NULL; + for(n=0;n<s->nb_streams;n++) { + stream = &s->streams[n]->codec; + bitrate += stream->bit_rate; + if (stream->codec_type == CODEC_TYPE_VIDEO) + video_enc = stream; + } + + if (!video_enc) { + av_free(avi); + return -1; + } + nb_frames = 0; + + put_le32(pb, (UINT32)(INT64_C(1000000) * FRAME_RATE_BASE / video_enc->frame_rate)); + put_le32(pb, bitrate / 8); /* XXX: not quite exact */ + put_le32(pb, 0); /* padding */ + put_le32(pb, AVIF_TRUSTCKTYPE | AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* flags */ + avi->frames_hdr_all = url_ftell(pb); /* remember this offset to fill later */ + put_le32(pb, nb_frames); /* nb frames, filled later */ + put_le32(pb, 0); /* initial frame */ + put_le32(pb, s->nb_streams); /* nb streams */ + put_le32(pb, 1024 * 1024); /* suggested buffer size */ + put_le32(pb, video_enc->width); + put_le32(pb, video_enc->height); + put_le32(pb, 0); /* reserved */ + put_le32(pb, 0); /* reserved */ + put_le32(pb, 0); /* reserved */ + put_le32(pb, 0); /* reserved */ + + /* stream list */ + for(i=0;i<n;i++) { + list2 = start_tag(pb, "LIST"); + put_tag(pb, "strl"); + + stream = &s->streams[i]->codec; + + /* stream generic header */ + strh = start_tag(pb, "strh"); + switch(stream->codec_type) { + case CODEC_TYPE_VIDEO: + put_tag(pb, "vids"); + put_le32(pb, codec_get_bmp_tag(stream->codec_id)); + put_le32(pb, 0); /* flags */ + put_le16(pb, 0); /* priority */ + put_le16(pb, 0); /* language */ + put_le32(pb, 0); /* initial frame */ + put_le32(pb, 1000); /* scale */ + put_le32(pb, (1000 * stream->frame_rate) / FRAME_RATE_BASE); /* rate */ + put_le32(pb, 0); /* start */ + avi->frames_hdr_strm[i] = url_ftell(pb); /* remember this offset to fill later */ + put_le32(pb, nb_frames); /* length, XXX: fill later */ + put_le32(pb, 1024 * 1024); /* suggested buffer size */ + put_le32(pb, -1); /* quality */ + put_le32(pb, stream->width * stream->height * 3); /* sample size */ + put_le16(pb, 0); + put_le16(pb, 0); + put_le16(pb, stream->width); + put_le16(pb, stream->height); + break; + case CODEC_TYPE_AUDIO: + put_tag(pb, "auds"); + put_le32(pb, 1); /* tag */ + put_le32(pb, 0); /* flags */ + put_le16(pb, 0); /* priority */ + put_le16(pb, 0); /* language */ + put_le32(pb, 0); /* initial frame */ + parse_specific_params(stream, &au_byterate, &au_ssize, &au_scale); + put_le32(pb, au_scale); /* scale */ + put_le32(pb, au_byterate); /* rate */ + put_le32(pb, 0); /* start */ + avi->frames_hdr_strm[i] = url_ftell(pb); /* remember this offset to fill later */ + put_le32(pb, 0); /* length, XXX: filled later */ + put_le32(pb, 12 * 1024); /* suggested buffer size */ + put_le32(pb, -1); /* quality */ + put_le32(pb, au_ssize); /* sample size */ + put_le32(pb, 0); + put_le32(pb, 0); + break; + default: + av_abort(); + } + end_tag(pb, strh); + + strf = start_tag(pb, "strf"); + switch(stream->codec_type) { + case CODEC_TYPE_VIDEO: + put_bmp_header(pb, stream, codec_bmp_tags, 0); + break; + case CODEC_TYPE_AUDIO: + if (put_wav_header(pb, stream) < 0) { + av_free(avi); + return -1; + } + break; + default: + av_abort(); + } + end_tag(pb, strf); + end_tag(pb, list2); + } + + end_tag(pb, list1); + + avi->movi_list = start_tag(pb, "LIST"); + avi->first = NULL; + avi->last = NULL; + put_tag(pb, "movi"); + + put_flush_packet(pb); + + return 0; +} + +static int avi_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AVIContext *avi = s->priv_data; + ByteIOContext *pb = &s->pb; + AVIIndex *idx; + unsigned char tag[5]; + unsigned int flags; + AVCodecContext *enc; + + enc = &s->streams[stream_index]->codec; + + tag[0] = '0'; + tag[1] = '0' + stream_index; + if (enc->codec_type == CODEC_TYPE_VIDEO) { + tag[2] = 'd'; + tag[3] = 'c'; + flags = enc->key_frame ? 0x10 : 0x00; + } else { + tag[2] = 'w'; + tag[3] = 'b'; + flags = 0x10; + } + if (enc->codec_type == CODEC_TYPE_AUDIO) + avi->audio_strm_length[stream_index] += size; + + if (!url_is_streamed(&s->pb)) { + idx = av_malloc(sizeof(AVIIndex)); + memcpy(idx->tag, tag, 4); + idx->flags = flags; + idx->pos = url_ftell(pb) - avi->movi_list; + idx->len = size; + idx->next = NULL; + if (!avi->last) + avi->first = idx; + else + avi->last->next = idx; + avi->last = idx; + } + + put_buffer(pb, tag, 4); + put_le32(pb, size); + put_buffer(pb, buf, size); + if (size & 1) + put_byte(pb, 0); + + put_flush_packet(pb); + return 0; +} + +static int avi_write_trailer(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + AVIContext *avi = s->priv_data; + offset_t file_size, idx_chunk; + int n, nb_frames, au_byterate, au_ssize, au_scale; + AVCodecContext *stream; + AVIIndex *idx; + + if (!url_is_streamed(&s->pb)) { + end_tag(pb, avi->movi_list); + + idx_chunk = start_tag(pb, "idx1"); + idx = avi->first; + while (idx != NULL) { + put_buffer(pb, idx->tag, 4); + put_le32(pb, idx->flags); + put_le32(pb, idx->pos); + put_le32(pb, idx->len); + idx = idx->next; + } + end_tag(pb, idx_chunk); + + /* update file size */ + file_size = url_ftell(pb); + url_fseek(pb, 4, SEEK_SET); + put_le32(pb, (UINT32)(file_size - 8)); + + /* Fill in frame/sample counters */ + nb_frames = 0; + for(n=0;n<s->nb_streams;n++) { + if (avi->frames_hdr_strm[n] != 0) { + stream = &s->streams[n]->codec; + url_fseek(pb, avi->frames_hdr_strm[n], SEEK_SET); + if (stream->codec_type == CODEC_TYPE_VIDEO) { + put_le32(pb, stream->frame_number); + if (nb_frames < stream->frame_number) + nb_frames = stream->frame_number; + } else { + if (stream->codec_id == CODEC_ID_MP2 || stream->codec_id == CODEC_ID_MP3LAME) { + put_le32(pb, stream->frame_number); + nb_frames += stream->frame_number; + } else { + parse_specific_params(stream, &au_byterate, &au_ssize, &au_scale); + put_le32(pb, avi->audio_strm_length[n] / au_ssize); + } + } + } + } + if (avi->frames_hdr_all != 0) { + url_fseek(pb, avi->frames_hdr_all, SEEK_SET); + put_le32(pb, nb_frames); + } + url_fseek(pb, file_size, SEEK_SET); + } + put_flush_packet(pb); + return 0; +} + +static AVOutputFormat avi_oformat = { + "avi", + "avi format", + "video/x-msvideo", + "avi", + sizeof(AVIContext), + CODEC_ID_MP2, + CODEC_ID_MSMPEG4V3, + avi_write_header, + avi_write_packet, + avi_write_trailer, +}; + +int avienc_init(void) +{ + av_register_output_format(&avi_oformat); + return 0; +} diff --git a/libavformat/avio.c b/libavformat/avio.c new file mode 100644 index 0000000000..37af56f9af --- /dev/null +++ b/libavformat/avio.c @@ -0,0 +1,156 @@ +/* + * Unbuffered io for ffmpeg system + * Copyright (c) 2001 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +URLProtocol *first_protocol = NULL; + +int register_protocol(URLProtocol *protocol) +{ + URLProtocol **p; + p = &first_protocol; + while (*p != NULL) p = &(*p)->next; + *p = protocol; + protocol->next = NULL; + return 0; +} + +int url_open(URLContext **puc, const char *filename, int flags) +{ + URLContext *uc; + URLProtocol *up; + const char *p; + char proto_str[128], *q; + int err; + + p = filename; + q = proto_str; + while (*p != '\0' && *p != ':') { + if ((q - proto_str) < sizeof(proto_str) - 1) + *q++ = *p; + p++; + } + /* if the protocol has length 1, we consider it is a dos drive */ + if (*p == '\0' || (q - proto_str) <= 1) { + strcpy(proto_str, "file"); + } else { + *q = '\0'; + } + + up = first_protocol; + while (up != NULL) { + if (!strcmp(proto_str, up->name)) + goto found; + up = up->next; + } + err = -ENOENT; + goto fail; + found: + uc = av_malloc(sizeof(URLContext)); + if (!uc) { + err = -ENOMEM; + goto fail; + } + uc->prot = up; + uc->flags = flags; + uc->is_streamed = 0; /* default = not streamed */ + uc->max_packet_size = 0; /* default: stream file */ + err = up->url_open(uc, filename, flags); + if (err < 0) { + av_free(uc); + *puc = NULL; + return err; + } + *puc = uc; + return 0; + fail: + *puc = NULL; + return err; +} + +int url_read(URLContext *h, unsigned char *buf, int size) +{ + int ret; + if (h->flags & URL_WRONLY) + return -EIO; + ret = h->prot->url_read(h, buf, size); + return ret; +} + +int url_write(URLContext *h, unsigned char *buf, int size) +{ + int ret; + if (!(h->flags & (URL_WRONLY | URL_RDWR))) + return -EIO; + /* avoid sending too big packets */ + if (h->max_packet_size && size > h->max_packet_size) + return -EIO; + ret = h->prot->url_write(h, buf, size); + return ret; +} + +offset_t url_seek(URLContext *h, offset_t pos, int whence) +{ + offset_t ret; + + if (!h->prot->url_seek) + return -EPIPE; + ret = h->prot->url_seek(h, pos, whence); + return ret; +} + +int url_close(URLContext *h) +{ + int ret; + + ret = h->prot->url_close(h); + av_free(h); + return ret; +} + +int url_exist(const char *filename) +{ + URLContext *h; + if (url_open(&h, filename, URL_RDONLY) < 0) + return 0; + url_close(h); + return 1; +} + +offset_t url_filesize(URLContext *h) +{ + offset_t pos, size; + + pos = url_seek(h, 0, SEEK_CUR); + size = url_seek(h, 0, SEEK_END); + url_seek(h, pos, SEEK_SET); + return size; +} + +/* + * Return the maximum packet size associated to packetized file + * handle. If the file is not packetized (stream like http or file on + * disk), then 0 is returned. + * + * @param h file handle + * @return maximum packet size in bytes + */ +int url_get_max_packet_size(URLContext *h) +{ + return h->max_packet_size; +} diff --git a/libavformat/avio.h b/libavformat/avio.h new file mode 100644 index 0000000000..541eff5ae0 --- /dev/null +++ b/libavformat/avio.h @@ -0,0 +1,153 @@ +#ifndef AVIO_H +#define AVIO_H + +/* output byte stream handling */ + +typedef INT64 offset_t; + +/* unbuffered I/O */ + +struct URLContext { + struct URLProtocol *prot; + int flags; + int is_streamed; /* true if streamed (no seek possible), default = false */ + int max_packet_size; /* if non zero, the stream is packetized with this max packet size */ + void *priv_data; +}; + +typedef struct URLContext URLContext; + +typedef struct URLPollEntry { + URLContext *handle; + int events; + int revents; +} URLPollEntry; + +#define URL_RDONLY 0 +#define URL_WRONLY 1 +#define URL_RDWR 2 + +int url_open(URLContext **h, const char *filename, int flags); +int url_read(URLContext *h, unsigned char *buf, int size); +int url_write(URLContext *h, unsigned char *buf, int size); +offset_t url_seek(URLContext *h, offset_t pos, int whence); +int url_close(URLContext *h); +int url_exist(const char *filename); +offset_t url_filesize(URLContext *h); +int url_get_max_packet_size(URLContext *h); +/* not implemented */ +int url_poll(URLPollEntry *poll_table, int n, int timeout); + +typedef struct URLProtocol { + const char *name; + int (*url_open)(URLContext *h, const char *filename, int flags); + int (*url_read)(URLContext *h, unsigned char *buf, int size); + int (*url_write)(URLContext *h, unsigned char *buf, int size); + offset_t (*url_seek)(URLContext *h, offset_t pos, int whence); + int (*url_close)(URLContext *h); + struct URLProtocol *next; +} URLProtocol; + +extern URLProtocol *first_protocol; + +int register_protocol(URLProtocol *protocol); + +typedef struct { + unsigned char *buffer; + int buffer_size; + unsigned char *buf_ptr, *buf_end; + void *opaque; + int (*read_packet)(void *opaque, UINT8 *buf, int buf_size); + void (*write_packet)(void *opaque, UINT8 *buf, int buf_size); + int (*seek)(void *opaque, offset_t offset, int whence); + offset_t pos; /* position in the file of the current buffer */ + int must_flush; /* true if the next seek should flush */ + int eof_reached; /* true if eof reached */ + int write_flag; /* true if open for writing */ + int is_streamed; + int max_packet_size; +} ByteIOContext; + +int init_put_byte(ByteIOContext *s, + unsigned char *buffer, + int buffer_size, + int write_flag, + void *opaque, + int (*read_packet)(void *opaque, UINT8 *buf, int buf_size), + void (*write_packet)(void *opaque, UINT8 *buf, int buf_size), + int (*seek)(void *opaque, offset_t offset, int whence)); + +void put_byte(ByteIOContext *s, int b); +void put_buffer(ByteIOContext *s, const unsigned char *buf, int size); +void put_le64(ByteIOContext *s, UINT64 val); +void put_be64(ByteIOContext *s, UINT64 val); +void put_le32(ByteIOContext *s, unsigned int val); +void put_be32(ByteIOContext *s, unsigned int val); +void put_le16(ByteIOContext *s, unsigned int val); +void put_be16(ByteIOContext *s, unsigned int val); +void put_tag(ByteIOContext *s, const char *tag); + +void put_be64_double(ByteIOContext *s, double val); +void put_strz(ByteIOContext *s, const char *buf); + +offset_t url_fseek(ByteIOContext *s, offset_t offset, int whence); +void url_fskip(ByteIOContext *s, offset_t offset); +offset_t url_ftell(ByteIOContext *s); +int url_feof(ByteIOContext *s); + +#define URL_EOF (-1) +int url_fgetc(ByteIOContext *s); +int url_fprintf(ByteIOContext *s, const char *fmt, ...); +char *url_fgets(ByteIOContext *s, char *buf, int buf_size); + +void put_flush_packet(ByteIOContext *s); + +int get_buffer(ByteIOContext *s, unsigned char *buf, int size); +int get_byte(ByteIOContext *s); +unsigned int get_le32(ByteIOContext *s); +UINT64 get_le64(ByteIOContext *s); +unsigned int get_le16(ByteIOContext *s); + +double get_be64_double(ByteIOContext *s); +char *get_strz(ByteIOContext *s, char *buf, int maxlen); +unsigned int get_be16(ByteIOContext *s); +unsigned int get_be32(ByteIOContext *s); +UINT64 get_be64(ByteIOContext *s); + +static inline int url_is_streamed(ByteIOContext *s) +{ + return s->is_streamed; +} + +int url_fdopen(ByteIOContext *s, URLContext *h); +int url_setbufsize(ByteIOContext *s, int buf_size); +int url_fopen(ByteIOContext *s, const char *filename, int flags); +int url_fclose(ByteIOContext *s); +URLContext *url_fileno(ByteIOContext *s); +int url_fget_max_packet_size(ByteIOContext *s); + +int url_open_buf(ByteIOContext *s, UINT8 *buf, int buf_size, int flags); +int url_close_buf(ByteIOContext *s); + +int url_open_dyn_buf(ByteIOContext *s); +int url_open_dyn_packet_buf(ByteIOContext *s, int max_packet_size); +int url_close_dyn_buf(ByteIOContext *s, UINT8 **pbuffer); + +/* file.c */ +extern URLProtocol file_protocol; +extern URLProtocol pipe_protocol; + +/* udp.c */ +extern URLProtocol udp_protocol; +int udp_set_remote_url(URLContext *h, const char *uri); +int udp_get_local_port(URLContext *h); +int udp_get_file_handle(URLContext *h); + +/* tcp.c */ +extern URLProtocol tcp_protocol; + +/* http.c */ +extern URLProtocol http_protocol; + +#endif + diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c new file mode 100644 index 0000000000..2e931bd849 --- /dev/null +++ b/libavformat/aviobuf.c @@ -0,0 +1,687 @@ +/* + * Buffered I/O for ffmpeg system + * Copyright (c) 2000,2001 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <stdarg.h> + +#define IO_BUFFER_SIZE 32768 + +int init_put_byte(ByteIOContext *s, + unsigned char *buffer, + int buffer_size, + int write_flag, + void *opaque, + int (*read_packet)(void *opaque, UINT8 *buf, int buf_size), + void (*write_packet)(void *opaque, UINT8 *buf, int buf_size), + int (*seek)(void *opaque, offset_t offset, int whence)) +{ + s->buffer = buffer; + s->buffer_size = buffer_size; + s->buf_ptr = buffer; + s->write_flag = write_flag; + if (!s->write_flag) + s->buf_end = buffer; + else + s->buf_end = buffer + buffer_size; + s->opaque = opaque; + s->write_packet = write_packet; + s->read_packet = read_packet; + s->seek = seek; + s->pos = 0; + s->must_flush = 0; + s->eof_reached = 0; + s->is_streamed = 0; + s->max_packet_size = 0; + return 0; +} + + +static void flush_buffer(ByteIOContext *s) +{ + if (s->buf_ptr > s->buffer) { + if (s->write_packet) + s->write_packet(s->opaque, s->buffer, s->buf_ptr - s->buffer); + s->pos += s->buf_ptr - s->buffer; + } + s->buf_ptr = s->buffer; +} + +void put_byte(ByteIOContext *s, int b) +{ + *(s->buf_ptr)++ = b; + if (s->buf_ptr >= s->buf_end) + flush_buffer(s); +} + +void put_buffer(ByteIOContext *s, const unsigned char *buf, int size) +{ + int len; + + while (size > 0) { + len = (s->buf_end - s->buf_ptr); + if (len > size) + len = size; + memcpy(s->buf_ptr, buf, len); + s->buf_ptr += len; + + if (s->buf_ptr >= s->buf_end) + flush_buffer(s); + + buf += len; + size -= len; + } +} + +void put_flush_packet(ByteIOContext *s) +{ + flush_buffer(s); + s->must_flush = 0; +} + +offset_t url_fseek(ByteIOContext *s, offset_t offset, int whence) +{ + offset_t offset1; + + if (whence != SEEK_CUR && whence != SEEK_SET) + return -EINVAL; + + if (s->write_flag) { + if (whence == SEEK_CUR) { + offset1 = s->pos + (s->buf_ptr - s->buffer); + if (offset == 0) + return offset1; + offset += offset1; + } + offset1 = offset - s->pos; + if (!s->must_flush && + offset1 >= 0 && offset1 < (s->buf_end - s->buffer)) { + /* can do the seek inside the buffer */ + s->buf_ptr = s->buffer + offset1; + } else { + if (!s->seek) + return -EPIPE; + flush_buffer(s); + s->must_flush = 1; + s->buf_ptr = s->buffer; + s->seek(s->opaque, offset, SEEK_SET); + s->pos = offset; + } + } else { + if (whence == SEEK_CUR) { + offset1 = s->pos - (s->buf_end - s->buffer) + (s->buf_ptr - s->buffer); + if (offset == 0) + return offset1; + offset += offset1; + } + offset1 = offset - (s->pos - (s->buf_end - s->buffer)); + if (offset1 >= 0 && offset1 <= (s->buf_end - s->buffer)) { + /* can do the seek inside the buffer */ + s->buf_ptr = s->buffer + offset1; + } else { + if (!s->seek) + return -EPIPE; + s->buf_ptr = s->buffer; + s->buf_end = s->buffer; + s->seek(s->opaque, offset, SEEK_SET); + s->pos = offset; + } + s->eof_reached = 0; + } + return offset; +} + +void url_fskip(ByteIOContext *s, offset_t offset) +{ + url_fseek(s, offset, SEEK_CUR); +} + +offset_t url_ftell(ByteIOContext *s) +{ + return url_fseek(s, 0, SEEK_CUR); +} + +int url_feof(ByteIOContext *s) +{ + return s->eof_reached; +} + +void put_le32(ByteIOContext *s, unsigned int val) +{ + put_byte(s, val); + put_byte(s, val >> 8); + put_byte(s, val >> 16); + put_byte(s, val >> 24); +} + +void put_be32(ByteIOContext *s, unsigned int val) +{ + put_byte(s, val >> 24); + put_byte(s, val >> 16); + put_byte(s, val >> 8); + put_byte(s, val); +} + +/* IEEE format is assumed */ +void put_be64_double(ByteIOContext *s, double val) +{ + union { + double d; + UINT64 ull; + } u; + u.d = val; + put_be64(s, u.ull); +} + +void put_strz(ByteIOContext *s, const char *str) +{ + if (str) + put_buffer(s, (const unsigned char *) str, strlen(str) + 1); + else + put_byte(s, 0); +} + +void put_le64(ByteIOContext *s, UINT64 val) +{ + put_le32(s, (UINT32)(val & 0xffffffff)); + put_le32(s, (UINT32)(val >> 32)); +} + +void put_be64(ByteIOContext *s, UINT64 val) +{ + put_be32(s, (UINT32)(val >> 32)); + put_be32(s, (UINT32)(val & 0xffffffff)); +} + +void put_le16(ByteIOContext *s, unsigned int val) +{ + put_byte(s, val); + put_byte(s, val >> 8); +} + +void put_be16(ByteIOContext *s, unsigned int val) +{ + put_byte(s, val >> 8); + put_byte(s, val); +} + +void put_tag(ByteIOContext *s, const char *tag) +{ + while (*tag) { + put_byte(s, *tag++); + } +} + +/* Input stream */ + +static void fill_buffer(ByteIOContext *s) +{ + int len; + + /* no need to do anything if EOF already reached */ + if (s->eof_reached) + return; + len = s->read_packet(s->opaque, s->buffer, s->buffer_size); + if (len <= 0) { + /* do not modify buffer if EOF reached so that a seek back can + be done without rereading data */ + s->eof_reached = 1; + } else { + s->pos += len; + s->buf_ptr = s->buffer; + s->buf_end = s->buffer + len; + } +} + +/* NOTE: return 0 if EOF, so you cannot use it if EOF handling is + necessary */ +/* XXX: put an inline version */ +int get_byte(ByteIOContext *s) +{ + if (s->buf_ptr < s->buf_end) { + return *s->buf_ptr++; + } else { + fill_buffer(s); + if (s->buf_ptr < s->buf_end) + return *s->buf_ptr++; + else + return 0; + } +} + +/* NOTE: return URL_EOF (-1) if EOF */ +int url_fgetc(ByteIOContext *s) +{ + if (s->buf_ptr < s->buf_end) { + return *s->buf_ptr++; + } else { + fill_buffer(s); + if (s->buf_ptr < s->buf_end) + return *s->buf_ptr++; + else + return URL_EOF; + } +} + +int get_buffer(ByteIOContext *s, unsigned char *buf, int size) +{ + int len, size1; + + size1 = size; + while (size > 0) { + len = s->buf_end - s->buf_ptr; + if (len > size) + len = size; + if (len == 0) { + fill_buffer(s); + len = s->buf_end - s->buf_ptr; + if (len == 0) + break; + } else { + memcpy(buf, s->buf_ptr, len); + buf += len; + s->buf_ptr += len; + size -= len; + } + } + return size1 - size; +} + +unsigned int get_le16(ByteIOContext *s) +{ + unsigned int val; + val = get_byte(s); + val |= get_byte(s) << 8; + return val; +} + +unsigned int get_le32(ByteIOContext *s) +{ + unsigned int val; + val = get_byte(s); + val |= get_byte(s) << 8; + val |= get_byte(s) << 16; + val |= get_byte(s) << 24; + return val; +} + +UINT64 get_le64(ByteIOContext *s) +{ + UINT64 val; + val = (UINT64)get_le32(s); + val |= (UINT64)get_le32(s) << 32; + return val; +} + +unsigned int get_be16(ByteIOContext *s) +{ + unsigned int val; + val = get_byte(s) << 8; + val |= get_byte(s); + return val; +} + +unsigned int get_be32(ByteIOContext *s) +{ + unsigned int val; + val = get_byte(s) << 24; + val |= get_byte(s) << 16; + val |= get_byte(s) << 8; + val |= get_byte(s); + return val; +} + +double get_be64_double(ByteIOContext *s) +{ + union { + double d; + UINT64 ull; + } u; + + u.ull = get_be64(s); + return u.d; +} + +char *get_strz(ByteIOContext *s, char *buf, int maxlen) +{ + int i = 0; + char c; + + while ((c = get_byte(s))) { + if (i < maxlen-1) + buf[i++] = c; + } + + buf[i] = 0; /* Ensure null terminated, but may be truncated */ + + return buf; +} + +UINT64 get_be64(ByteIOContext *s) +{ + UINT64 val; + val = (UINT64)get_be32(s) << 32; + val |= (UINT64)get_be32(s); + return val; +} + +/* link with avio functions */ + +void url_write_packet(void *opaque, UINT8 *buf, int buf_size) +{ + URLContext *h = opaque; + url_write(h, buf, buf_size); +} + +int url_read_packet(void *opaque, UINT8 *buf, int buf_size) +{ + URLContext *h = opaque; + return url_read(h, buf, buf_size); +} + +int url_seek_packet(void *opaque, INT64 offset, int whence) +{ + URLContext *h = opaque; + url_seek(h, offset, whence); + return 0; +} + +int url_fdopen(ByteIOContext *s, URLContext *h) +{ + UINT8 *buffer; + int buffer_size, max_packet_size; + + + max_packet_size = url_get_max_packet_size(h); + if (max_packet_size) { + buffer_size = max_packet_size; /* no need to bufferize more than one packet */ + } else { + buffer_size = IO_BUFFER_SIZE; + } + buffer = av_malloc(buffer_size); + if (!buffer) + return -ENOMEM; + + if (init_put_byte(s, buffer, buffer_size, + (h->flags & URL_WRONLY) != 0, h, + url_read_packet, url_write_packet, url_seek_packet) < 0) { + av_free(buffer); + return -EIO; + } + s->is_streamed = h->is_streamed; + s->max_packet_size = max_packet_size; + return 0; +} + +/* XXX: must be called before any I/O */ +int url_setbufsize(ByteIOContext *s, int buf_size) +{ + UINT8 *buffer; + buffer = av_malloc(buf_size); + if (!buffer) + return -ENOMEM; + + av_free(s->buffer); + s->buffer = buffer; + s->buffer_size = buf_size; + s->buf_ptr = buffer; + if (!s->write_flag) + s->buf_end = buffer; + else + s->buf_end = buffer + buf_size; + return 0; +} + +/* NOTE: when opened as read/write, the buffers are only used for + reading */ +int url_fopen(ByteIOContext *s, const char *filename, int flags) +{ + URLContext *h; + int err; + + err = url_open(&h, filename, flags); + if (err < 0) + return err; + err = url_fdopen(s, h); + if (err < 0) { + url_close(h); + return err; + } + return 0; +} + +int url_fclose(ByteIOContext *s) +{ + URLContext *h = s->opaque; + + av_free(s->buffer); + memset(s, 0, sizeof(ByteIOContext)); + return url_close(h); +} + +URLContext *url_fileno(ByteIOContext *s) +{ + return s->opaque; +} + +/* XXX: currently size is limited */ +int url_fprintf(ByteIOContext *s, const char *fmt, ...) +{ + va_list ap; + char buf[4096]; + int ret; + + va_start(ap, fmt); + ret = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + put_buffer(s, buf, strlen(buf)); + return ret; +} + +/* note: unlike fgets, the EOL character is not returned and a whole + line is parsed. return NULL if first char read was EOF */ +char *url_fgets(ByteIOContext *s, char *buf, int buf_size) +{ + int c; + char *q; + + c = url_fgetc(s); + if (c == EOF) + return NULL; + q = buf; + for(;;) { + if (c == EOF || c == '\n') + break; + if ((q - buf) < buf_size - 1) + *q++ = c; + c = url_fgetc(s); + } + if (buf_size > 0) + *q = '\0'; + return buf; +} + +/* + * Return the maximum packet size associated to packetized buffered file + * handle. If the file is not packetized (stream like http or file on + * disk), then 0 is returned. + * + * @param h buffered file handle + * @return maximum packet size in bytes + */ +int url_fget_max_packet_size(ByteIOContext *s) +{ + return s->max_packet_size; +} + +/* buffer handling */ +int url_open_buf(ByteIOContext *s, UINT8 *buf, int buf_size, int flags) +{ + return init_put_byte(s, buf, buf_size, + (flags & URL_WRONLY) != 0, NULL, NULL, NULL, NULL); +} + +/* return the written or read size */ +int url_close_buf(ByteIOContext *s) +{ + put_flush_packet(s); + return s->buf_ptr - s->buffer; +} + +/* output in a dynamic buffer */ + +typedef struct DynBuffer { + int pos, size, allocated_size; + UINT8 *buffer; + int io_buffer_size; + UINT8 io_buffer[1]; +} DynBuffer; + +static void dyn_buf_write(void *opaque, UINT8 *buf, int buf_size) +{ + DynBuffer *d = opaque; + int new_size, new_allocated_size; + UINT8 *new_buffer; + + /* reallocate buffer if needed */ + new_size = d->pos + buf_size; + new_allocated_size = d->allocated_size; + while (new_size > new_allocated_size) { + if (!new_allocated_size) + new_allocated_size = new_size; + else + new_allocated_size = (new_allocated_size * 3) / 2 + 1; + } + + if (new_allocated_size > d->allocated_size) { + new_buffer = av_malloc(new_allocated_size); + if (!new_buffer) + return; + memcpy(new_buffer, d->buffer, d->size); + av_free(d->buffer); + d->buffer = new_buffer; + d->allocated_size = new_allocated_size; + } + memcpy(d->buffer + d->pos, buf, buf_size); + d->pos = new_size; + if (d->pos > d->size) + d->size = d->pos; +} + +static void dyn_packet_buf_write(void *opaque, UINT8 *buf, int buf_size) +{ + unsigned char buf1[4]; + + /* packetized write: output the header */ + buf1[0] = (buf_size >> 24); + buf1[1] = (buf_size >> 16); + buf1[2] = (buf_size >> 8); + buf1[3] = (buf_size); + dyn_buf_write(opaque, buf1, 4); + + /* then the data */ + dyn_buf_write(opaque, buf, buf_size); +} + +static int dyn_buf_seek(void *opaque, offset_t offset, int whence) +{ + DynBuffer *d = opaque; + + if (whence == SEEK_CUR) + offset += d->pos; + else if (whence == SEEK_END) + offset += d->size; + if (offset < 0 || offset > 0x7fffffffLL) + return -1; + d->pos = offset; + return 0; +} + +static int url_open_dyn_buf_internal(ByteIOContext *s, int max_packet_size) +{ + DynBuffer *d; + int io_buffer_size, ret; + + if (max_packet_size) + io_buffer_size = max_packet_size; + else + io_buffer_size = 1024; + + d = av_malloc(sizeof(DynBuffer) + io_buffer_size); + if (!d) + return -1; + d->io_buffer_size = io_buffer_size; + d->buffer = NULL; + d->pos = 0; + d->size = 0; + d->allocated_size = 0; + ret = init_put_byte(s, d->io_buffer, io_buffer_size, + 1, d, NULL, + max_packet_size ? dyn_packet_buf_write : dyn_buf_write, + max_packet_size ? NULL : dyn_buf_seek); + if (ret == 0) { + s->max_packet_size = max_packet_size; + } + return ret; +} + +/* + * Open a write only memory stream. + * + * @param s new IO context + * @return zero if no error. + */ +int url_open_dyn_buf(ByteIOContext *s) +{ + return url_open_dyn_buf_internal(s, 0); +} + +/* + * Open a write only packetized memory stream with a maximum packet + * size of 'max_packet_size'. The stream is stored in a memory buffer + * with a big endian 4 byte header giving the packet size in bytes. + * + * @param s new IO context + * @param max_packet_size maximum packet size (must be > 0) + * @return zero if no error. + */ +int url_open_dyn_packet_buf(ByteIOContext *s, int max_packet_size) +{ + if (max_packet_size <= 0) + return -1; + return url_open_dyn_buf_internal(s, max_packet_size); +} + +/* + * Return the written size and a pointer to the buffer. The buffer + * must be freed with av_free(). + * @param s IO context + * @param pointer to a byte buffer + * @return the length of the byte buffer + */ +int url_close_dyn_buf(ByteIOContext *s, UINT8 **pbuffer) +{ + DynBuffer *d = s->opaque; + int size; + + put_flush_packet(s); + + *pbuffer = d->buffer; + size = d->size; + av_free(d); + return size; +} diff --git a/libavformat/barpainet.c b/libavformat/barpainet.c new file mode 100644 index 0000000000..c1e8877718 --- /dev/null +++ b/libavformat/barpainet.c @@ -0,0 +1,25 @@ + +#include <stdlib.h> +#include <strings.h> +#include "barpainet.h" + +int inet_aton (const char * str, struct in_addr * add) { + const char * pch = str; + unsigned int add1 = 0, add2 = 0, add3 = 0, add4 = 0; + + add1 = atoi(pch); + pch = strpbrk(pch,"."); + if (pch == 0 || ++pch == 0) goto done; + add2 = atoi(pch); + pch = strpbrk(pch,"."); + if (pch == 0 || ++pch == 0) goto done; + add3 = atoi(pch); + pch = strpbrk(pch,"."); + if (pch == 0 || ++pch == 0) goto done; + add4 = atoi(pch); + +done: + add->s_addr=(add4<<24)+(add3<<16)+(add2<<8)+add1; + + return 1; +} diff --git a/libavformat/barpainet.h b/libavformat/barpainet.h new file mode 100644 index 0000000000..461403b3fa --- /dev/null +++ b/libavformat/barpainet.h @@ -0,0 +1,23 @@ +#ifndef BARPA_INET_H +#define BARPA_INET_H + +#include "../config.h" + +#ifdef CONFIG_BEOS_NETSERVER + +# include <socket.h> +int inet_aton (const char * str, struct in_addr * add); +# define PF_INET AF_INET +# define SO_SNDBUF 0x40000001 + +/* fake */ +struct ip_mreq { + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_interface; /* local IP address of interface */ +}; + +#else +# include <arpa/inet.h> +#endif + +#endif /* BARPA_INET_H */ diff --git a/libavformat/beosaudio.cpp b/libavformat/beosaudio.cpp new file mode 100644 index 0000000000..a1ae0a53c8 --- /dev/null +++ b/libavformat/beosaudio.cpp @@ -0,0 +1,449 @@ +/* + * BeOS audio play interface + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +#include <Application.h> +#include <SoundPlayer.h> + +extern "C" { +#include "avformat.h" +} + +/* enable performance checks */ +//#define PERF_CHECK + +//const char *audio_device = "/dev/dsp"; +const char *audio_device = "beosaudio:"; + +/* Pipes are 4k in BeOS IIRC */ +#define AUDIO_BLOCK_SIZE 4096 +//#define AUDIO_BLOCK_SIZE 2048 +#define AUDIO_BLOCK_COUNT 8 + +#define AUDIO_BUFFER_SIZE (AUDIO_BLOCK_SIZE*AUDIO_BLOCK_COUNT) + +/* pipes suck for realtime */ +#define USE_RING_BUFFER 1 + +typedef struct { + int fd; + int sample_rate; + int channels; + int frame_size; /* in bytes ! */ + CodecID codec_id; + int flip_left : 1; + UINT8 buffer[AUDIO_BUFFER_SIZE]; + int buffer_ptr; + int pipefd; /* the other end of the pipe */ + /* ring buffer */ + sem_id input_sem; + int input_index; + sem_id output_sem; + int output_index; + int queued; + BSoundPlayer *player; + int has_quit; /* signal callbacks not to wait */ + volatile bigtime_t starve_time; +} AudioData; + +static thread_id main_thid; +static thread_id bapp_thid; +static int own_BApp_created = 0; +static int refcount = 0; + +/* create the BApplication and Run() it */ +static int32 bapp_thread(void *arg) +{ + new BApplication("application/x-vnd.ffmpeg"); + own_BApp_created = 1; + be_app->Run(); + /* kill the process group */ +// kill(0, SIGINT); +// kill(main_thid, SIGHUP); + return B_OK; +} + +/* create the BApplication only if needed */ +static void create_bapp_if_needed(void) +{ + if (refcount++ == 0) { + /* needed by libmedia */ + if (be_app == NULL) { + bapp_thid = spawn_thread(bapp_thread, "ffmpeg BApplication", B_NORMAL_PRIORITY, NULL); + resume_thread(bapp_thid); + while (!own_BApp_created) + snooze(50000); + } + } +} + +static void destroy_bapp_if_needed(void) +{ + if (--refcount == 0 && own_BApp_created) { + be_app->Lock(); + be_app->Quit(); + be_app = NULL; + } +} + +/* called back by BSoundPlayer */ +static void audioplay_callback(void *cookie, void *buffer, size_t bufferSize, const media_raw_audio_format &format) +{ + AudioData *s; + size_t len, amount; + unsigned char *buf = (unsigned char *)buffer; + + s = (AudioData *)cookie; + if (s->has_quit) + return; + while (bufferSize > 0) { +#ifdef PERF_CHECK + bigtime_t t; + t = system_time(); +#endif +#ifdef USE_RING_BUFFER + len = MIN(AUDIO_BLOCK_SIZE, bufferSize); + if (acquire_sem_etc(s->output_sem, len, B_CAN_INTERRUPT, 0LL) < B_OK) { + s->has_quit = 1; + s->player->SetHasData(false); + return; + } + amount = MIN(len, (AUDIO_BUFFER_SIZE - s->output_index)); + memcpy(buf, &s->buffer[s->output_index], amount); + s->output_index += amount; + if (s->output_index >= AUDIO_BUFFER_SIZE) { + s->output_index %= AUDIO_BUFFER_SIZE; + memcpy(buf + amount, &s->buffer[s->output_index], len - amount); + s->output_index += len-amount; + s->output_index %= AUDIO_BUFFER_SIZE; + } + release_sem_etc(s->input_sem, len, 0); +#else + len = read(s->pipefd, buf, bufferSize); +#endif +#ifdef PERF_CHECK + t = system_time() - t; + s->starve_time = MAX(s->starve_time, t); +#endif +#ifndef USE_RING_BUFFER + if (len < B_OK) { + puts("EPIPE"); + s->player->SetHasData(false); + snooze(100000); + return; + } + if (len == 0) { + s->player->SetHasData(false); + snooze(100000); + return; + } +#endif + buf += len; + bufferSize -= len; + } +} + +static int audio_open(AudioData *s, int is_output) +{ + int p[2]; + int ret; + media_raw_audio_format format; + + if (!is_output) + return -EIO; /* not for now */ +#ifdef USE_RING_BUFFER + s->input_sem = create_sem(AUDIO_BUFFER_SIZE, "ffmpeg_ringbuffer_input"); +// s->input_sem = create_sem(AUDIO_BLOCK_SIZE, "ffmpeg_ringbuffer_input"); + if (s->input_sem < B_OK) + return -EIO; + s->output_sem = create_sem(0, "ffmpeg_ringbuffer_output"); + if (s->output_sem < B_OK) { + delete_sem(s->input_sem); + return -EIO; + } + s->input_index = 0; + s->output_index = 0; + s->queued = 0; +#else + ret = pipe(p); + if (ret < 0) + return -EIO; + s->fd = p[is_output?1:0]; + s->pipefd = p[is_output?0:1]; + if (s->fd < 0) { + perror(is_output?"audio out":"audio in"); + return -EIO; + } +#endif + create_bapp_if_needed(); + /* non blocking mode */ +// fcntl(s->fd, F_SETFL, O_NONBLOCK); +// fcntl(s->pipefd, F_SETFL, O_NONBLOCK); + s->frame_size = AUDIO_BLOCK_SIZE; + format = media_raw_audio_format::wildcard; + format.format = media_raw_audio_format::B_AUDIO_SHORT; + format.byte_order = B_HOST_IS_LENDIAN ? B_MEDIA_LITTLE_ENDIAN : B_MEDIA_BIG_ENDIAN; + format.channel_count = s->channels; + format.buffer_size = s->frame_size; + format.frame_rate = s->sample_rate; + s->player = new BSoundPlayer(&format, "ffmpeg output", audioplay_callback); + if (s->player->InitCheck() != B_OK) { + delete s->player; + s->player = NULL; +#ifdef USE_RING_BUFFER + if (s->input_sem) + delete_sem(s->input_sem); + if (s->output_sem) + delete_sem(s->output_sem); +#else + close(s->fd); + close(s->pipefd); +#endif + return -EIO; + } + s->player->SetCookie(s); + s->player->SetVolume(1.0); + s->player->Start(); + s->player->SetHasData(true); + /* bump up the priority (avoid realtime though) */ + set_thread_priority(find_thread(NULL), B_DISPLAY_PRIORITY+1); + return 0; +} + +static int audio_close(AudioData *s) +{ +#ifdef USE_RING_BUFFER + if (s->input_sem) + delete_sem(s->input_sem); + if (s->output_sem) + delete_sem(s->output_sem); +#endif + s->has_quit = 1; + if (s->player) { + s->player->Stop(); + } + if (s->player) + delete s->player; +#ifndef USE_RING_BUFFER + close(s->pipefd); + close(s->fd); +#endif + destroy_bapp_if_needed(); + return 0; +} + +/* sound output support */ +static int audio_write_header(AVFormatContext *s1) +{ + AudioData *s = (AudioData *)s1->priv_data; + AVStream *st; + int ret; + + st = s1->streams[0]; + s->sample_rate = st->codec.sample_rate; + s->channels = st->codec.channels; + ret = audio_open(s, 1); + if (ret < 0) + return -EIO; + return 0; +} + +static int audio_write_packet(AVFormatContext *s1, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AudioData *s = (AudioData *)s1->priv_data; + int len, ret; +#ifdef PERF_CHECK + bigtime_t t = s->starve_time; + s->starve_time = 0; + printf("starve_time: %lld \n", t); +#endif +#ifdef USE_RING_BUFFER + while (size > 0) { + int amount; + len = MIN(size, AUDIO_BLOCK_SIZE); + if (acquire_sem_etc(s->input_sem, len, B_CAN_INTERRUPT, 0LL) < B_OK) + return -EIO; + amount = MIN(len, (AUDIO_BUFFER_SIZE - s->input_index)); + memcpy(&s->buffer[s->input_index], buf, amount); + s->input_index += amount; + if (s->input_index >= AUDIO_BUFFER_SIZE) { + s->input_index %= AUDIO_BUFFER_SIZE; + memcpy(&s->buffer[s->input_index], buf + amount, len - amount); + s->input_index += len - amount; + } + release_sem_etc(s->output_sem, len, 0); + buf += len; + size -= len; + } +#else + while (size > 0) { + len = AUDIO_BLOCK_SIZE - s->buffer_ptr; + if (len > size) + len = size; + memcpy(s->buffer + s->buffer_ptr, buf, len); + s->buffer_ptr += len; + if (s->buffer_ptr >= AUDIO_BLOCK_SIZE) { + for(;;) { +//snooze(1000); + ret = write(s->fd, s->buffer, AUDIO_BLOCK_SIZE); + if (ret != 0) + break; + if (ret < 0 && (errno != EAGAIN && errno != EINTR)) + return -EIO; + } + s->buffer_ptr = 0; + } + buf += len; + size -= len; + } +#endif + return 0; +} + +static int audio_write_trailer(AVFormatContext *s1) +{ + AudioData *s = (AudioData *)s1->priv_data; + + audio_close(s); + return 0; +} + +/* grab support */ + +static int audio_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + AudioData *s = (AudioData *)s1->priv_data; + AVStream *st; + int ret; + + if (!ap || ap->sample_rate <= 0 || ap->channels <= 0) + return -1; + + st = av_new_stream(s1, 0); + if (!st) { + return -ENOMEM; + } + s->sample_rate = ap->sample_rate; + s->channels = ap->channels; + + ret = audio_open(s, 0); + if (ret < 0) { + av_free(st); + return -EIO; + } else { + /* take real parameters */ + st->codec.codec_type = CODEC_TYPE_AUDIO; + st->codec.codec_id = s->codec_id; + st->codec.sample_rate = s->sample_rate; + st->codec.channels = s->channels; + return 0; + } +} + +static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + AudioData *s = (AudioData *)s1->priv_data; + int ret; + + if (av_new_packet(pkt, s->frame_size) < 0) + return -EIO; + for(;;) { + ret = read(s->fd, pkt->data, pkt->size); + if (ret > 0) + break; + if (ret == -1 && (errno == EAGAIN || errno == EINTR)) { + av_free_packet(pkt); + pkt->size = 0; + return 0; + } + if (!(ret == 0 || (ret == -1 && (errno == EAGAIN || errno == EINTR)))) { + av_free_packet(pkt); + return -EIO; + } + } + pkt->size = ret; + if (s->flip_left && s->channels == 2) { + int i; + short *p = (short *) pkt->data; + + for (i = 0; i < ret; i += 4) { + *p = ~*p; + p += 2; + } + } + return 0; +} + +static int audio_read_close(AVFormatContext *s1) +{ + AudioData *s = (AudioData *)s1->priv_data; + + audio_close(s); + return 0; +} + +AVInputFormat audio_in_format = { + "audio_device", + "audio grab and output", + sizeof(AudioData), + NULL, + audio_read_header, + audio_read_packet, + audio_read_close, + NULL, + AVFMT_NOFILE, +}; + +AVOutputFormat audio_out_format = { + "audio_device", + "audio grab and output", + "", + "", + sizeof(AudioData), +#ifdef WORDS_BIGENDIAN + CODEC_ID_PCM_S16BE, +#else + CODEC_ID_PCM_S16LE, +#endif + CODEC_ID_NONE, + audio_write_header, + audio_write_packet, + audio_write_trailer, + AVFMT_NOFILE, +}; + +extern "C" { + +int audio_init(void) +{ + main_thid = find_thread(NULL); + av_register_input_format(&audio_in_format); + av_register_output_format(&audio_out_format); + return 0; +} + +} // "C" + diff --git a/libavformat/crc.c b/libavformat/crc.c new file mode 100644 index 0000000000..553ab537cb --- /dev/null +++ b/libavformat/crc.c @@ -0,0 +1,111 @@ +/* + * CRC decoder (for codec/format testing) + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf) {s1 += *buf++; s2 += s1;} +#define DO2(buf) DO1(buf); DO1(buf); +#define DO4(buf) DO2(buf); DO2(buf); +#define DO8(buf) DO4(buf); DO4(buf); +#define DO16(buf) DO8(buf); DO8(buf); + +static UINT32 adler32(UINT32 adler, UINT8 *buf, unsigned int len) +{ + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == NULL) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + DO16(buf); + k -= 16; + } + if (k != 0) do { + DO1(buf); + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} + +typedef struct CRCState { + UINT32 crcval; +} CRCState; + +static int crc_write_header(struct AVFormatContext *s) +{ + CRCState *crc = s->priv_data; + + /* init CRC */ + crc->crcval = adler32(0, NULL, 0); + + return 0; +} + +static int crc_write_packet(struct AVFormatContext *s, + int stream_index, + unsigned char *buf, int size, int force_pts) +{ + CRCState *crc = s->priv_data; + crc->crcval = adler32(crc->crcval, buf, size); + return 0; +} + +static int crc_write_trailer(struct AVFormatContext *s) +{ + CRCState *crc = s->priv_data; + char buf[64]; + + snprintf(buf, sizeof(buf), "CRC=%08x\n", crc->crcval); + put_buffer(&s->pb, buf, strlen(buf)); + put_flush_packet(&s->pb); + return 0; +} + +static AVOutputFormat crc_format = { + "crc", + "crc testing format", + NULL, + "", + sizeof(CRCState), + CODEC_ID_PCM_S16LE, + CODEC_ID_RAWVIDEO, + crc_write_header, + crc_write_packet, + crc_write_trailer, +}; + +int crc_init(void) +{ + av_register_output_format(&crc_format); + return 0; +} diff --git a/libavformat/cutils.c b/libavformat/cutils.c new file mode 100644 index 0000000000..ce2c845226 --- /dev/null +++ b/libavformat/cutils.c @@ -0,0 +1,110 @@ +/* + * Various simple utilities for ffmpeg system + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <ctype.h> + +#if !defined(CONFIG_NOCUTILS) +/** + * Return TRUE if val is a prefix of str. If it returns TRUE, ptr is + * set to the next character in 'str' after the prefix. + * + * @param str input string + * @param val prefix to test + * @param ptr updated after the prefix in str in there is a match + * @return TRUE if there is a match + */ +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +/** + * Return TRUE if val is a prefix of str (case independent). If it + * returns TRUE, ptr is set to the next character in 'str' after the + * prefix. + * + * @param str input string + * @param val prefix to test + * @param ptr updated after the prefix in str in there is a match + * @return TRUE if there is a match */ +int stristart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (toupper(*(unsigned char *)p) != toupper(*(unsigned char *)q)) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +/** + * Copy the string str to buf. If str length is bigger than buf_size - + * 1 then it is clamped to buf_size - 1. + * NOTE: this function does what strncpy should have done to be + * useful. NEVER use strncpy. + * + * @param buf destination buffer + * @param buf_size size of destination buffer + * @param str source string + */ +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +#endif diff --git a/libavformat/dv.c b/libavformat/dv.c new file mode 100644 index 0000000000..1f152a2fba --- /dev/null +++ b/libavformat/dv.c @@ -0,0 +1,134 @@ +/* + * Raw DV format + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#define NTSC_FRAME_SIZE 120000 +#define PAL_FRAME_SIZE 144000 + +typedef struct DVDemuxContext { + int is_audio; +} DVDemuxContext; + +/* raw input */ +static int dv_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + AVStream *vst, *ast; + + vst = av_new_stream(s, 0); + if (!vst) + return AVERROR_NOMEM; + vst->codec.codec_type = CODEC_TYPE_VIDEO; + vst->codec.codec_id = CODEC_ID_DVVIDEO; + +#if 0 + ast = av_new_stream(s, 1); + if (!ast) + return AVERROR_NOMEM; + + ast->codec.codec_type = CODEC_TYPE_AUDIO; + ast->codec.codec_id = CODEC_ID_DVAUDIO; +#endif + return 0; +} + +/* XXX: build fake audio stream when DV audio decoder will be finished */ +static int dv_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int ret, size, dsf; + uint8_t buf[4]; + + ret = get_buffer(&s->pb, buf, 4); + if (ret <= 0) + return -EIO; + dsf = buf[3] & 0x80; + if (!dsf) + size = NTSC_FRAME_SIZE; + else + size = PAL_FRAME_SIZE; + + if (av_new_packet(pkt, size) < 0) + return -EIO; + + pkt->stream_index = 0; + memcpy(pkt->data, buf, 4); + ret = get_buffer(&s->pb, pkt->data + 4, size - 4); + if (ret <= 0) { + av_free_packet(pkt); + return -EIO; + } + return ret; +} + +static int dv_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat dv_iformat = { + "dv", + "DV video format", + sizeof(DVDemuxContext), + NULL, + dv_read_header, + dv_read_packet, + dv_read_close, + .extensions = "dv", +}; + +#if 0 +int dv_write_header(struct AVFormatContext *s) +{ + return 0; +} + +int dv_write_packet(struct AVFormatContext *s, + int stream_index, + unsigned char *buf, int size, int force_pts) +{ + put_buffer(&s->pb, buf, size); + put_flush_packet(&s->pb); + return 0; +} + +int dv_write_trailer(struct AVFormatContext *s) +{ + return 0; +} + +AVOutputFormat dv_oformat = { + "dv", + "DV video format", + NULL, + "dv", + 0, + CODEC_ID_DVVIDEO, + CODEC_ID_DVAUDIO, + dv_write_header, + dv_write_packet, + dv_write_trailer, +}; +#endif + +int dv_init(void) +{ + av_register_input_format(&dv_iformat); + // av_register_output_format(&dv_oformat); + return 0; +} diff --git a/libavformat/ffm.c b/libavformat/ffm.c new file mode 100644 index 0000000000..c21599c69f --- /dev/null +++ b/libavformat/ffm.c @@ -0,0 +1,684 @@ +/* + * FFM (ffserver live feed) encoder and decoder + * Copyright (c) 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <unistd.h> + +/* The FFM file is made of blocks of fixed size */ +#define FFM_HEADER_SIZE 14 +#define PACKET_ID 0x666d + +/* each packet contains frames (which can span several packets */ +#define FRAME_HEADER_SIZE 8 +#define FLAG_KEY_FRAME 0x01 + +typedef struct FFMStream { + INT64 pts; +} FFMStream; + +enum { + READ_HEADER, + READ_DATA, +}; + +typedef struct FFMContext { + /* only reading mode */ + offset_t write_index, file_size; + int read_state; + UINT8 header[FRAME_HEADER_SIZE]; + + /* read and write */ + int first_packet; /* true if first packet, needed to set the discontinuity tag */ + int packet_size; + int frame_offset; + INT64 pts; + UINT8 *packet_ptr, *packet_end; + UINT8 packet[FFM_PACKET_SIZE]; +} FFMContext; + +/* disable pts hack for testing */ +int ffm_nopts = 0; + +static void flush_packet(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + int fill_size, h; + ByteIOContext *pb = &s->pb; + + fill_size = ffm->packet_end - ffm->packet_ptr; + memset(ffm->packet_ptr, 0, fill_size); + + /* put header */ + put_be16(pb, PACKET_ID); + put_be16(pb, fill_size); + put_be64(pb, ffm->pts); + h = ffm->frame_offset; + if (ffm->first_packet) + h |= 0x8000; + put_be16(pb, h); + put_buffer(pb, ffm->packet, ffm->packet_end - ffm->packet); + + /* prepare next packet */ + ffm->frame_offset = 0; /* no key frame */ + ffm->pts = 0; /* no pts */ + ffm->packet_ptr = ffm->packet; + ffm->first_packet = 0; +} + +/* 'first' is true if first data of a frame */ +static void ffm_write_data(AVFormatContext *s, + UINT8 *buf, int size, + INT64 pts, int first) +{ + FFMContext *ffm = s->priv_data; + int len; + + if (first && ffm->frame_offset == 0) + ffm->frame_offset = ffm->packet_ptr - ffm->packet + FFM_HEADER_SIZE; + if (first && ffm->pts == 0) + ffm->pts = pts; + + /* write as many packets as needed */ + while (size > 0) { + len = ffm->packet_end - ffm->packet_ptr; + if (len > size) + len = size; + memcpy(ffm->packet_ptr, buf, len); + + ffm->packet_ptr += len; + buf += len; + size -= len; + if (ffm->packet_ptr >= ffm->packet_end) { + /* special case : no pts in packet : we leave the current one */ + if (ffm->pts == 0) + ffm->pts = pts; + + flush_packet(s); + } + } +} + +static int ffm_write_header(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + AVStream *st; + FFMStream *fst; + ByteIOContext *pb = &s->pb; + AVCodecContext *codec; + int bit_rate, i; + + ffm->packet_size = FFM_PACKET_SIZE; + + /* header */ + put_tag(pb, "FFM1"); + put_be32(pb, ffm->packet_size); + /* XXX: store write position in other file ? */ + put_be64(pb, ffm->packet_size); /* current write position */ + + put_be32(pb, s->nb_streams); + bit_rate = 0; + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + bit_rate += st->codec.bit_rate; + } + put_be32(pb, bit_rate); + + /* list of streams */ + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + fst = av_mallocz(sizeof(FFMStream)); + if (!fst) + goto fail; + st->priv_data = fst; + + codec = &st->codec; + /* generic info */ + put_be32(pb, codec->codec_id); + put_byte(pb, codec->codec_type); + put_be32(pb, codec->bit_rate); + put_be32(pb, codec->quality); + put_be32(pb, codec->flags); + /* specific info */ + switch(codec->codec_type) { + case CODEC_TYPE_VIDEO: + put_be32(pb, (codec->frame_rate * 1000) / FRAME_RATE_BASE); + put_be16(pb, codec->width); + put_be16(pb, codec->height); + put_be16(pb, codec->gop_size); + put_byte(pb, codec->qmin); + put_byte(pb, codec->qmax); + put_byte(pb, codec->max_qdiff); + put_be16(pb, (int) (codec->qcompress * 10000.0)); + put_be16(pb, (int) (codec->qblur * 10000.0)); + put_be32(pb, codec->bit_rate_tolerance); + put_strz(pb, codec->rc_eq); + put_be32(pb, codec->rc_max_rate); + put_be32(pb, codec->rc_min_rate); + put_be32(pb, codec->rc_buffer_size); + put_be64_double(pb, codec->i_quant_factor); + put_be64_double(pb, codec->b_quant_factor); + put_be64_double(pb, codec->i_quant_offset); + put_be64_double(pb, codec->b_quant_offset); + put_be32(pb, codec->dct_algo); + break; + case CODEC_TYPE_AUDIO: + put_be32(pb, codec->sample_rate); + put_le16(pb, codec->channels); + put_le16(pb, codec->frame_size); + break; + default: + av_abort(); + } + /* hack to have real time */ + if (ffm_nopts) + fst->pts = 0; + else + fst->pts = av_gettime(); + } + + /* flush until end of block reached */ + while ((url_ftell(pb) % ffm->packet_size) != 0) + put_byte(pb, 0); + + put_flush_packet(pb); + + /* init packet mux */ + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet + ffm->packet_size - FFM_HEADER_SIZE; + ffm->frame_offset = 0; + ffm->pts = 0; + ffm->first_packet = 1; + + return 0; + fail: + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + av_freep(&st->priv_data); + } + return -1; +} + +static int ffm_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AVStream *st = s->streams[stream_index]; + FFMStream *fst = st->priv_data; + INT64 pts; + UINT8 header[FRAME_HEADER_SIZE]; + int duration; + + if (st->codec.codec_type == CODEC_TYPE_AUDIO) { + duration = ((float)st->codec.frame_size / st->codec.sample_rate * 1000000.0); + } else { + duration = (1000000.0 * FRAME_RATE_BASE / (float)st->codec.frame_rate); + } + + pts = fst->pts; + /* packet size & key_frame */ + header[0] = stream_index; + header[1] = 0; + if (st->codec.key_frame) + header[1] |= FLAG_KEY_FRAME; + header[2] = (size >> 16) & 0xff; + header[3] = (size >> 8) & 0xff; + header[4] = size & 0xff; + header[5] = (duration >> 16) & 0xff; + header[6] = (duration >> 8) & 0xff; + header[7] = duration & 0xff; + ffm_write_data(s, header, FRAME_HEADER_SIZE, pts, 1); + ffm_write_data(s, buf, size, pts, 0); + + fst->pts += duration; + return 0; +} + +static int ffm_write_trailer(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + FFMContext *ffm = s->priv_data; + int i; + + /* flush packets */ + if (ffm->packet_ptr > ffm->packet) + flush_packet(s); + + put_flush_packet(pb); + + if (!url_is_streamed(pb)) { + INT64 size; + /* update the write offset */ + size = url_ftell(pb); + url_fseek(pb, 8, SEEK_SET); + put_be64(pb, size); + put_flush_packet(pb); + } + + for(i=0;i<s->nb_streams;i++) + av_freep(&s->streams[i]->priv_data); + return 0; +} + +/* ffm demux */ + +static int ffm_is_avail_data(AVFormatContext *s, int size) +{ + FFMContext *ffm = s->priv_data; + offset_t pos, avail_size; + int len; + + len = ffm->packet_end - ffm->packet_ptr; + if (!ffm_nopts) { + /* XXX: I don't understand this test, so I disabled it for testing */ + if (size <= len) + return 1; + } + pos = url_ftell(&s->pb); + if (pos == ffm->write_index) { + /* exactly at the end of stream */ + return 0; + } else if (pos < ffm->write_index) { + avail_size = ffm->write_index - pos; + } else { + avail_size = (ffm->file_size - pos) + (ffm->write_index - FFM_PACKET_SIZE); + } + avail_size = (avail_size / ffm->packet_size) * (ffm->packet_size - FFM_HEADER_SIZE) + len; + if (size <= avail_size) + return 1; + else + return 0; +} + +/* first is true if we read the frame header */ +static int ffm_read_data(AVFormatContext *s, + UINT8 *buf, int size, int first) +{ + FFMContext *ffm = s->priv_data; + ByteIOContext *pb = &s->pb; + int len, fill_size, size1, frame_offset; + + size1 = size; + while (size > 0) { + redo: + len = ffm->packet_end - ffm->packet_ptr; + if (len > size) + len = size; + if (len == 0) { + if (url_ftell(pb) == ffm->file_size) + url_fseek(pb, ffm->packet_size, SEEK_SET); + retry_read: + get_be16(pb); /* PACKET_ID */ + fill_size = get_be16(pb); + ffm->pts = get_be64(pb); + frame_offset = get_be16(pb); + get_buffer(pb, ffm->packet, ffm->packet_size - FFM_HEADER_SIZE); + ffm->packet_end = ffm->packet + (ffm->packet_size - FFM_HEADER_SIZE - fill_size); + /* if first packet or resynchronization packet, we must + handle it specifically */ + if (ffm->first_packet || (frame_offset & 0x8000)) { + if (!frame_offset) { + /* This packet has no frame headers in it */ + if (url_ftell(pb) >= ffm->packet_size * 3) { + url_fseek(pb, -ffm->packet_size * 2, SEEK_CUR); + goto retry_read; + } + /* This is bad, we cannot find a valid frame header */ + return 0; + } + ffm->first_packet = 0; + if ((frame_offset & 0x7ffff) < FFM_HEADER_SIZE) + av_abort(); + ffm->packet_ptr = ffm->packet + (frame_offset & 0x7fff) - FFM_HEADER_SIZE; + if (!first) + break; + } else { + ffm->packet_ptr = ffm->packet; + } + goto redo; + } + memcpy(buf, ffm->packet_ptr, len); + buf += len; + ffm->packet_ptr += len; + size -= len; + first = 0; + } + return size1 - size; +} + + +static int ffm_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + FFMContext *ffm = s->priv_data; + AVStream *st; + FFMStream *fst; + ByteIOContext *pb = &s->pb; + AVCodecContext *codec; + int i; + UINT32 tag; + + /* header */ + tag = get_le32(pb); + if (tag != MKTAG('F', 'F', 'M', '1')) + goto fail; + ffm->packet_size = get_be32(pb); + if (ffm->packet_size != FFM_PACKET_SIZE) + goto fail; + ffm->write_index = get_be64(pb); + /* get also filesize */ + if (!url_is_streamed(pb)) { + ffm->file_size = url_filesize(url_fileno(pb)); + } else { + ffm->file_size = (UINT64_C(1) << 63) - 1; + } + + s->nb_streams = get_be32(pb); + get_be32(pb); /* total bitrate */ + /* read each stream */ + for(i=0;i<s->nb_streams;i++) { + char rc_eq_buf[128]; + + st = av_mallocz(sizeof(AVStream)); + if (!st) + goto fail; + s->streams[i] = st; + fst = av_mallocz(sizeof(FFMStream)); + if (!fst) + goto fail; + st->priv_data = fst; + + codec = &st->codec; + /* generic info */ + st->codec.codec_id = get_be32(pb); + st->codec.codec_type = get_byte(pb); /* codec_type */ + codec->bit_rate = get_be32(pb); + codec->quality = get_be32(pb); + codec->flags = get_be32(pb); + /* specific info */ + switch(codec->codec_type) { + case CODEC_TYPE_VIDEO: + codec->frame_rate = ((INT64)get_be32(pb) * FRAME_RATE_BASE) / 1000; + codec->width = get_be16(pb); + codec->height = get_be16(pb); + codec->gop_size = get_be16(pb); + codec->qmin = get_byte(pb); + codec->qmax = get_byte(pb); + codec->max_qdiff = get_byte(pb); + codec->qcompress = get_be16(pb) / 10000.0; + codec->qblur = get_be16(pb) / 10000.0; + codec->bit_rate_tolerance = get_be32(pb); + codec->rc_eq = strdup(get_strz(pb, rc_eq_buf, sizeof(rc_eq_buf))); + codec->rc_max_rate = get_be32(pb); + codec->rc_min_rate = get_be32(pb); + codec->rc_buffer_size = get_be32(pb); + codec->i_quant_factor = get_be64_double(pb); + codec->b_quant_factor = get_be64_double(pb); + codec->i_quant_offset = get_be64_double(pb); + codec->b_quant_offset = get_be64_double(pb); + codec->dct_algo = get_be32(pb); + break; + case CODEC_TYPE_AUDIO: + codec->sample_rate = get_be32(pb); + codec->channels = get_le16(pb); + codec->frame_size = get_le16(pb); + break; + default: + goto fail; + } + + } + + /* get until end of block reached */ + while ((url_ftell(pb) % ffm->packet_size) != 0) + get_byte(pb); + + /* init packet demux */ + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet; + ffm->frame_offset = 0; + ffm->pts = 0; + ffm->read_state = READ_HEADER; + ffm->first_packet = 1; + return 0; + fail: + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + if (st) { + av_freep(&st->priv_data); + av_free(st); + } + } + return -1; +} + +/* return < 0 if eof */ +static int ffm_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int size; + FFMContext *ffm = s->priv_data; + int duration; + + switch(ffm->read_state) { + case READ_HEADER: + if (!ffm_is_avail_data(s, FRAME_HEADER_SIZE)) { + return -EAGAIN; + } +#if 0 + printf("pos=%08Lx spos=%Lx, write_index=%Lx size=%Lx\n", + url_ftell(&s->pb), s->pb.pos, ffm->write_index, ffm->file_size); +#endif + if (ffm_read_data(s, ffm->header, FRAME_HEADER_SIZE, 1) != + FRAME_HEADER_SIZE) + return -EAGAIN; +#if 0 + { + int i; + for(i=0;i<FRAME_HEADER_SIZE;i++) + printf("%02x ", ffm->header[i]); + printf("\n"); + } +#endif + ffm->read_state = READ_DATA; + /* fall thru */ + case READ_DATA: + size = (ffm->header[2] << 16) | (ffm->header[3] << 8) | ffm->header[4]; + if (!ffm_is_avail_data(s, size)) { + return -EAGAIN; + } + + duration = (ffm->header[5] << 16) | (ffm->header[6] << 8) | ffm->header[7]; + + av_new_packet(pkt, size); + pkt->stream_index = ffm->header[0]; + if (ffm->header[1] & FLAG_KEY_FRAME) + pkt->flags |= PKT_FLAG_KEY; + + ffm->read_state = READ_HEADER; + if (ffm_read_data(s, pkt->data, size, 0) != size) { + /* bad case: desynchronized packet. we cancel all the packet loading */ + av_free_packet(pkt); + return -EAGAIN; + } + pkt->pts = ffm->pts; + pkt->duration = duration; + break; + } + return 0; +} + +//#define DEBUG_SEEK + +/* pos is between 0 and file_size - FFM_PACKET_SIZE. It is translated + by the write position inside this function */ +static void ffm_seek1(AVFormatContext *s, offset_t pos1) +{ + FFMContext *ffm = s->priv_data; + ByteIOContext *pb = &s->pb; + offset_t pos; + + pos = pos1 + ffm->write_index; + if (pos >= ffm->file_size) + pos -= (ffm->file_size - FFM_PACKET_SIZE); +#ifdef DEBUG_SEEK + printf("seek to %Lx -> %Lx\n", pos1, pos); +#endif + url_fseek(pb, pos, SEEK_SET); +} + +static INT64 get_pts(AVFormatContext *s, offset_t pos) +{ + ByteIOContext *pb = &s->pb; + INT64 pts; + + ffm_seek1(s, pos); + url_fskip(pb, 4); + pts = get_be64(pb); +#ifdef DEBUG_SEEK + printf("pts=%0.6f\n", pts / 1000000.0); +#endif + return pts; +} + +/* seek to a given time in the file. The file read pointer is + positionned at or before pts. XXX: the following code is quite + approximative */ +static int ffm_seek(AVFormatContext *s, INT64 wanted_pts) +{ + FFMContext *ffm = s->priv_data; + offset_t pos_min, pos_max, pos; + INT64 pts_min, pts_max, pts; + double pos1; + +#ifdef DEBUG_SEEK + printf("wanted_pts=%0.6f\n", wanted_pts / 1000000.0); +#endif + /* find the position using linear interpolation (better than + dichotomy in typical cases) */ + pos_min = 0; + pos_max = ffm->file_size - 2 * FFM_PACKET_SIZE; + while (pos_min <= pos_max) { + pts_min = get_pts(s, pos_min); + pts_max = get_pts(s, pos_max); + /* linear interpolation */ + pos1 = (double)(pos_max - pos_min) * (double)(wanted_pts - pts_min) / + (double)(pts_max - pts_min); + pos = (((INT64)pos1) / FFM_PACKET_SIZE) * FFM_PACKET_SIZE; + if (pos <= pos_min) + pos = pos_min; + else if (pos >= pos_max) + pos = pos_max; + pts = get_pts(s, pos); + /* check if we are lucky */ + if (pts == wanted_pts) { + goto found; + } else if (pts > wanted_pts) { + pos_max = pos - FFM_PACKET_SIZE; + } else { + pos_min = pos + FFM_PACKET_SIZE; + } + } + pos = pos_min; + if (pos > 0) + pos -= FFM_PACKET_SIZE; + found: + ffm_seek1(s, pos); + return 0; +} + +offset_t ffm_read_write_index(int fd) +{ + UINT8 buf[8]; + offset_t pos; + int i; + + lseek(fd, 8, SEEK_SET); + read(fd, buf, 8); + pos = 0; + for(i=0;i<8;i++) + pos |= buf[i] << (56 - i * 8); + return pos; +} + +void ffm_write_write_index(int fd, offset_t pos) +{ + UINT8 buf[8]; + int i; + + for(i=0;i<8;i++) + buf[i] = (pos >> (56 - i * 8)) & 0xff; + lseek(fd, 8, SEEK_SET); + write(fd, buf, 8); +} + +void ffm_set_write_index(AVFormatContext *s, offset_t pos, offset_t file_size) +{ + FFMContext *ffm = s->priv_data; + ffm->write_index = pos; + ffm->file_size = file_size; +} + +static int ffm_read_close(AVFormatContext *s) +{ + AVStream *st; + int i; + + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + av_freep(&st->priv_data); + } + return 0; +} + +static int ffm_probe(AVProbeData *p) +{ + if (p->buf_size >= 4 && + p->buf[0] == 'F' && p->buf[1] == 'F' && p->buf[2] == 'M' && + p->buf[3] == '1') + return AVPROBE_SCORE_MAX + 1; + return 0; +} + +static AVInputFormat ffm_iformat = { + "ffm", + "ffm format", + sizeof(FFMContext), + ffm_probe, + ffm_read_header, + ffm_read_packet, + ffm_read_close, + ffm_seek, +}; + +static AVOutputFormat ffm_oformat = { + "ffm", + "ffm format", + "", + "ffm", + sizeof(FFMContext), + /* not really used */ + CODEC_ID_MP2, + CODEC_ID_MPEG1VIDEO, + ffm_write_header, + ffm_write_packet, + ffm_write_trailer, +}; + +int ffm_init(void) +{ + av_register_input_format(&ffm_iformat); + av_register_output_format(&ffm_oformat); + return 0; +} diff --git a/libavformat/file.c b/libavformat/file.c new file mode 100644 index 0000000000..8206ff9255 --- /dev/null +++ b/libavformat/file.c @@ -0,0 +1,130 @@ +/* + * Buffered file io for ffmpeg system + * Copyright (c) 2001 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <fcntl.h> +#ifndef CONFIG_WIN32 +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#else +#include <io.h> +#define open(fname,oflag,pmode) _open(fname,oflag,pmode) +#endif /* CONFIG_WIN32 */ + + +/* standard file protocol */ + +static int file_open(URLContext *h, const char *filename, int flags) +{ + int access; + int fd; + + if (flags & URL_WRONLY) { + access = O_CREAT | O_TRUNC | O_WRONLY; + } else { + access = O_RDONLY; + } +#ifdef CONFIG_WIN32 + access |= O_BINARY; +#endif + fd = open(filename, access, 0666); + if (fd < 0) + return -ENOENT; + h->priv_data = (void *)fd; + return 0; +} + +static int file_read(URLContext *h, unsigned char *buf, int size) +{ + int fd = (int)h->priv_data; + return read(fd, buf, size); +} + +static int file_write(URLContext *h, unsigned char *buf, int size) +{ + int fd = (int)h->priv_data; + return write(fd, buf, size); +} + +/* XXX: use llseek */ +static offset_t file_seek(URLContext *h, offset_t pos, int whence) +{ + int fd = (int)h->priv_data; +#ifdef CONFIG_WIN32 + return _lseeki64(fd, pos, whence); +#else + return lseek(fd, pos, whence); +#endif +} + +static int file_close(URLContext *h) +{ + int fd = (int)h->priv_data; + return close(fd); +} + +URLProtocol file_protocol = { + "file", + file_open, + file_read, + file_write, + file_seek, + file_close, +}; + +/* pipe protocol */ + +static int pipe_open(URLContext *h, const char *filename, int flags) +{ + int fd; + + if (flags & URL_WRONLY) { + fd = 1; + } else { + fd = 0; + } + h->priv_data = (void *)fd; + return 0; +} + +static int pipe_read(URLContext *h, unsigned char *buf, int size) +{ + int fd = (int)h->priv_data; + return read(fd, buf, size); +} + +static int pipe_write(URLContext *h, unsigned char *buf, int size) +{ + int fd = (int)h->priv_data; + return write(fd, buf, size); +} + +static int pipe_close(URLContext *h) +{ + return 0; +} + +URLProtocol pipe_protocol = { + "pipe", + pipe_open, + pipe_read, + pipe_write, + NULL, + pipe_close, +}; diff --git a/libavformat/framehook.c b/libavformat/framehook.c new file mode 100644 index 0000000000..03ee32e188 --- /dev/null +++ b/libavformat/framehook.c @@ -0,0 +1,102 @@ +/* + * Video processing hooks + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <errno.h> +#include "config.h" +#include "framehook.h" +#include "avformat.h" + +#ifdef HAVE_VHOOK +#include <dlfcn.h> +#endif + + +typedef struct _FrameHookEntry { + struct _FrameHookEntry *next; + FrameHookConfigureFn Configure; + FrameHookProcessFn Process; + void *ctx; +} FrameHookEntry; + +static FrameHookEntry *first_hook; + +/* Returns 0 on OK */ +int frame_hook_add(int argc, char *argv[]) +{ +#ifdef HAVE_VHOOK + void *loaded; + FrameHookEntry *fhe, **fhep; + + if (argc < 1) { + return ENOENT; + } + + loaded = dlopen(argv[0], RTLD_NOW); + if (!loaded) { + fprintf(stderr, "%s\n", dlerror()); + return -1; + } + + fhe = av_mallocz(sizeof(*fhe)); + if (!fhe) { + return errno; + } + + fhe->Configure = dlsym(loaded, "Configure"); + fhe->Process = dlsym(loaded, "Process"); + + if (!fhe->Process) { + fprintf(stderr, "Failed to find Process entrypoint in %s\n", argv[0]); + return -1; + } + + if (!fhe->Configure && argc > 1) { + fprintf(stderr, "Failed to find Configure entrypoint in %s\n", argv[0]); + return -1; + } + + if (argc > 1 || fhe->Configure) { + if (fhe->Configure(&fhe->ctx, argc, argv)) { + fprintf(stderr, "Failed to Configure %s\n", argv[0]); + return -1; + } + } + + for (fhep = &first_hook; *fhep; fhep = &((*fhep)->next)) { + } + + *fhep = fhe; + + return 0; +#else + fprintf(stderr, "Video hooking not compiled into this version\n"); + return 1; +#endif +} + +void frame_hook_process(AVPicture *pict, enum PixelFormat pix_fmt, int width, int height) +{ + if (first_hook) { + FrameHookEntry *fhe; + INT64 pts = av_gettime(); + + for (fhe = first_hook; fhe; fhe = fhe->next) { + fhe->Process(fhe->ctx, pict, pix_fmt, width, height, pts); + } + } +} diff --git a/libavformat/framehook.h b/libavformat/framehook.h new file mode 100644 index 0000000000..eb1a51f7e4 --- /dev/null +++ b/libavformat/framehook.h @@ -0,0 +1,19 @@ +#ifndef _FRAMEHOOK_H +#define _FRAMEHOOK_H + +/* + * Prototypes for interface to .so that implement a video processing hook + */ + +#include "avcodec.h" + +/* Function must be called 'Configure' */ +typedef int (*FrameHookConfigureFn)(void **ctxp, int argc, char *argv[]); + +/* Function must be called 'Process' */ +typedef void (*FrameHookProcessFn)(void *ctx, struct AVPicture *pict, enum PixelFormat pix_fmt, int width, int height, INT64 pts); + +extern int frame_hook_add(int argc, char *argv[]); +extern void frame_hook_process(struct AVPicture *pict, enum PixelFormat pix_fmt, int width, int height); + +#endif diff --git a/libavformat/gif.c b/libavformat/gif.c new file mode 100644 index 0000000000..6becf52a49 --- /dev/null +++ b/libavformat/gif.c @@ -0,0 +1,375 @@ +/* + * Animated GIF encoder + * Copyright (c) 2000 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * First version by Francois Revol revol@free.fr + * + * Features and limitations: + * - currently no compression is performed, + * in fact the size of the data is 9/8 the size of the image in 8bpp + * - uses only a global standard palette + * - tested with IE 5.0, Opera for BeOS, NetPositive (BeOS), and Mozilla (BeOS). + * + * Reference documents: + * http://www.goice.co.jp/member/mo/formats/gif.html + * http://astronomy.swin.edu.au/pbourke/dataformats/gif/ + * http://www.dcs.ed.ac.uk/home/mxr/gfx/2d/GIF89a.txt + * + * this url claims to have an LZW algorithm not covered by Unisys patent: + * http://www.msg.net/utility/whirlgif/gifencod.html + * could help reduce the size of the files _a lot_... + * some sites mentions an RLE type compression also. + */ + +#include "avformat.h" + +/* bitstream minipacket size */ +#define GIF_CHUNKS 100 + +/* slows down the decoding (and some browsers doesn't like it) */ +/* #define GIF_ADD_APP_HEADER */ + +typedef struct { + unsigned char r; + unsigned char g; + unsigned char b; +} rgb_triplet; + +/* we use the standard 216 color palette */ + +/* this script was used to create the palette: + * for r in 00 33 66 99 cc ff; do for g in 00 33 66 99 cc ff; do echo -n " "; for b in 00 33 66 99 cc ff; do + * echo -n "{ 0x$r, 0x$g, 0x$b }, "; done; echo ""; done; done + */ + +static const rgb_triplet gif_clut[216] = { + { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x33 }, { 0x00, 0x00, 0x66 }, { 0x00, 0x00, 0x99 }, { 0x00, 0x00, 0xcc }, { 0x00, 0x00, 0xff }, + { 0x00, 0x33, 0x00 }, { 0x00, 0x33, 0x33 }, { 0x00, 0x33, 0x66 }, { 0x00, 0x33, 0x99 }, { 0x00, 0x33, 0xcc }, { 0x00, 0x33, 0xff }, + { 0x00, 0x66, 0x00 }, { 0x00, 0x66, 0x33 }, { 0x00, 0x66, 0x66 }, { 0x00, 0x66, 0x99 }, { 0x00, 0x66, 0xcc }, { 0x00, 0x66, 0xff }, + { 0x00, 0x99, 0x00 }, { 0x00, 0x99, 0x33 }, { 0x00, 0x99, 0x66 }, { 0x00, 0x99, 0x99 }, { 0x00, 0x99, 0xcc }, { 0x00, 0x99, 0xff }, + { 0x00, 0xcc, 0x00 }, { 0x00, 0xcc, 0x33 }, { 0x00, 0xcc, 0x66 }, { 0x00, 0xcc, 0x99 }, { 0x00, 0xcc, 0xcc }, { 0x00, 0xcc, 0xff }, + { 0x00, 0xff, 0x00 }, { 0x00, 0xff, 0x33 }, { 0x00, 0xff, 0x66 }, { 0x00, 0xff, 0x99 }, { 0x00, 0xff, 0xcc }, { 0x00, 0xff, 0xff }, + { 0x33, 0x00, 0x00 }, { 0x33, 0x00, 0x33 }, { 0x33, 0x00, 0x66 }, { 0x33, 0x00, 0x99 }, { 0x33, 0x00, 0xcc }, { 0x33, 0x00, 0xff }, + { 0x33, 0x33, 0x00 }, { 0x33, 0x33, 0x33 }, { 0x33, 0x33, 0x66 }, { 0x33, 0x33, 0x99 }, { 0x33, 0x33, 0xcc }, { 0x33, 0x33, 0xff }, + { 0x33, 0x66, 0x00 }, { 0x33, 0x66, 0x33 }, { 0x33, 0x66, 0x66 }, { 0x33, 0x66, 0x99 }, { 0x33, 0x66, 0xcc }, { 0x33, 0x66, 0xff }, + { 0x33, 0x99, 0x00 }, { 0x33, 0x99, 0x33 }, { 0x33, 0x99, 0x66 }, { 0x33, 0x99, 0x99 }, { 0x33, 0x99, 0xcc }, { 0x33, 0x99, 0xff }, + { 0x33, 0xcc, 0x00 }, { 0x33, 0xcc, 0x33 }, { 0x33, 0xcc, 0x66 }, { 0x33, 0xcc, 0x99 }, { 0x33, 0xcc, 0xcc }, { 0x33, 0xcc, 0xff }, + { 0x33, 0xff, 0x00 }, { 0x33, 0xff, 0x33 }, { 0x33, 0xff, 0x66 }, { 0x33, 0xff, 0x99 }, { 0x33, 0xff, 0xcc }, { 0x33, 0xff, 0xff }, + { 0x66, 0x00, 0x00 }, { 0x66, 0x00, 0x33 }, { 0x66, 0x00, 0x66 }, { 0x66, 0x00, 0x99 }, { 0x66, 0x00, 0xcc }, { 0x66, 0x00, 0xff }, + { 0x66, 0x33, 0x00 }, { 0x66, 0x33, 0x33 }, { 0x66, 0x33, 0x66 }, { 0x66, 0x33, 0x99 }, { 0x66, 0x33, 0xcc }, { 0x66, 0x33, 0xff }, + { 0x66, 0x66, 0x00 }, { 0x66, 0x66, 0x33 }, { 0x66, 0x66, 0x66 }, { 0x66, 0x66, 0x99 }, { 0x66, 0x66, 0xcc }, { 0x66, 0x66, 0xff }, + { 0x66, 0x99, 0x00 }, { 0x66, 0x99, 0x33 }, { 0x66, 0x99, 0x66 }, { 0x66, 0x99, 0x99 }, { 0x66, 0x99, 0xcc }, { 0x66, 0x99, 0xff }, + { 0x66, 0xcc, 0x00 }, { 0x66, 0xcc, 0x33 }, { 0x66, 0xcc, 0x66 }, { 0x66, 0xcc, 0x99 }, { 0x66, 0xcc, 0xcc }, { 0x66, 0xcc, 0xff }, + { 0x66, 0xff, 0x00 }, { 0x66, 0xff, 0x33 }, { 0x66, 0xff, 0x66 }, { 0x66, 0xff, 0x99 }, { 0x66, 0xff, 0xcc }, { 0x66, 0xff, 0xff }, + { 0x99, 0x00, 0x00 }, { 0x99, 0x00, 0x33 }, { 0x99, 0x00, 0x66 }, { 0x99, 0x00, 0x99 }, { 0x99, 0x00, 0xcc }, { 0x99, 0x00, 0xff }, + { 0x99, 0x33, 0x00 }, { 0x99, 0x33, 0x33 }, { 0x99, 0x33, 0x66 }, { 0x99, 0x33, 0x99 }, { 0x99, 0x33, 0xcc }, { 0x99, 0x33, 0xff }, + { 0x99, 0x66, 0x00 }, { 0x99, 0x66, 0x33 }, { 0x99, 0x66, 0x66 }, { 0x99, 0x66, 0x99 }, { 0x99, 0x66, 0xcc }, { 0x99, 0x66, 0xff }, + { 0x99, 0x99, 0x00 }, { 0x99, 0x99, 0x33 }, { 0x99, 0x99, 0x66 }, { 0x99, 0x99, 0x99 }, { 0x99, 0x99, 0xcc }, { 0x99, 0x99, 0xff }, + { 0x99, 0xcc, 0x00 }, { 0x99, 0xcc, 0x33 }, { 0x99, 0xcc, 0x66 }, { 0x99, 0xcc, 0x99 }, { 0x99, 0xcc, 0xcc }, { 0x99, 0xcc, 0xff }, + { 0x99, 0xff, 0x00 }, { 0x99, 0xff, 0x33 }, { 0x99, 0xff, 0x66 }, { 0x99, 0xff, 0x99 }, { 0x99, 0xff, 0xcc }, { 0x99, 0xff, 0xff }, + { 0xcc, 0x00, 0x00 }, { 0xcc, 0x00, 0x33 }, { 0xcc, 0x00, 0x66 }, { 0xcc, 0x00, 0x99 }, { 0xcc, 0x00, 0xcc }, { 0xcc, 0x00, 0xff }, + { 0xcc, 0x33, 0x00 }, { 0xcc, 0x33, 0x33 }, { 0xcc, 0x33, 0x66 }, { 0xcc, 0x33, 0x99 }, { 0xcc, 0x33, 0xcc }, { 0xcc, 0x33, 0xff }, + { 0xcc, 0x66, 0x00 }, { 0xcc, 0x66, 0x33 }, { 0xcc, 0x66, 0x66 }, { 0xcc, 0x66, 0x99 }, { 0xcc, 0x66, 0xcc }, { 0xcc, 0x66, 0xff }, + { 0xcc, 0x99, 0x00 }, { 0xcc, 0x99, 0x33 }, { 0xcc, 0x99, 0x66 }, { 0xcc, 0x99, 0x99 }, { 0xcc, 0x99, 0xcc }, { 0xcc, 0x99, 0xff }, + { 0xcc, 0xcc, 0x00 }, { 0xcc, 0xcc, 0x33 }, { 0xcc, 0xcc, 0x66 }, { 0xcc, 0xcc, 0x99 }, { 0xcc, 0xcc, 0xcc }, { 0xcc, 0xcc, 0xff }, + { 0xcc, 0xff, 0x00 }, { 0xcc, 0xff, 0x33 }, { 0xcc, 0xff, 0x66 }, { 0xcc, 0xff, 0x99 }, { 0xcc, 0xff, 0xcc }, { 0xcc, 0xff, 0xff }, + { 0xff, 0x00, 0x00 }, { 0xff, 0x00, 0x33 }, { 0xff, 0x00, 0x66 }, { 0xff, 0x00, 0x99 }, { 0xff, 0x00, 0xcc }, { 0xff, 0x00, 0xff }, + { 0xff, 0x33, 0x00 }, { 0xff, 0x33, 0x33 }, { 0xff, 0x33, 0x66 }, { 0xff, 0x33, 0x99 }, { 0xff, 0x33, 0xcc }, { 0xff, 0x33, 0xff }, + { 0xff, 0x66, 0x00 }, { 0xff, 0x66, 0x33 }, { 0xff, 0x66, 0x66 }, { 0xff, 0x66, 0x99 }, { 0xff, 0x66, 0xcc }, { 0xff, 0x66, 0xff }, + { 0xff, 0x99, 0x00 }, { 0xff, 0x99, 0x33 }, { 0xff, 0x99, 0x66 }, { 0xff, 0x99, 0x99 }, { 0xff, 0x99, 0xcc }, { 0xff, 0x99, 0xff }, + { 0xff, 0xcc, 0x00 }, { 0xff, 0xcc, 0x33 }, { 0xff, 0xcc, 0x66 }, { 0xff, 0xcc, 0x99 }, { 0xff, 0xcc, 0xcc }, { 0xff, 0xcc, 0xff }, + { 0xff, 0xff, 0x00 }, { 0xff, 0xff, 0x33 }, { 0xff, 0xff, 0x66 }, { 0xff, 0xff, 0x99 }, { 0xff, 0xff, 0xcc }, { 0xff, 0xff, 0xff }, +}; + +/* The GIF format uses reversed order for bitstreams... */ +/* at least they don't use PDP_ENDIAN :) */ +/* so we 'extend' PutBitContext. hmmm, OOP :) */ +/* seems this thing changed slightly since I wrote it... */ + +#ifdef ALT_BITSTREAM_WRITER +# error no ALT_BITSTREAM_WRITER support for now +#endif + +static void gif_put_bits_rev(PutBitContext *s, int n, unsigned int value) +{ + unsigned int bit_buf; + int bit_cnt; + +#ifdef STATS + st_out_bit_counts[st_current_index] += n; +#endif + // printf("put_bits=%d %x\n", n, value); + assert(n == 32 || value < (1U << n)); + + bit_buf = s->bit_buf; + bit_cnt = 32 - s->bit_left; /* XXX:lazyness... was = s->bit_cnt; */ + + // printf("n=%d value=%x cnt=%d buf=%x\n", n, value, bit_cnt, bit_buf); + /* XXX: optimize */ + if (n < (32-bit_cnt)) { + bit_buf |= value << (bit_cnt); + bit_cnt+=n; + } else { + bit_buf |= value << (bit_cnt); + + *s->buf_ptr = bit_buf & 0xff; + s->buf_ptr[1] = (bit_buf >> 8) & 0xff; + s->buf_ptr[2] = (bit_buf >> 16) & 0xff; + s->buf_ptr[3] = (bit_buf >> 24) & 0xff; + + //printf("bitbuf = %08x\n", bit_buf); + s->buf_ptr+=4; + if (s->buf_ptr >= s->buf_end) + puts("bit buffer overflow !!"); // should never happen ! who got rid of the callback ??? +// flush_buffer_rev(s); + bit_cnt=bit_cnt + n - 32; + if (bit_cnt == 0) { + bit_buf = 0; + } else { + bit_buf = value >> (n - bit_cnt); + } + } + + s->bit_buf = bit_buf; + s->bit_left = 32 - bit_cnt; +} + +/* pad the end of the output stream with zeros */ +static void gif_flush_put_bits_rev(PutBitContext *s) +{ + while (s->bit_left < 32) { + /* XXX: should test end of buffer */ + *s->buf_ptr++=s->bit_buf & 0xff; + s->bit_buf>>=8; + s->bit_left+=8; + } +// flush_buffer_rev(s); + s->bit_left=32; + s->bit_buf=0; +} + +/* !RevPutBitContext */ + +typedef struct { + UINT8 buffer[100]; /* data chunks */ + INT64 time, file_time; +} GIFContext; + +static int gif_write_header(AVFormatContext *s) +{ + GIFContext *gif = s->priv_data; + ByteIOContext *pb = &s->pb; + AVCodecContext *enc, *video_enc; + int i, width, height, rate; + +/* XXX: do we reject audio streams or just ignore them ? + if(s->nb_streams > 1) + return -1; +*/ + gif->time = 0; + gif->file_time = 0; + + video_enc = NULL; + for(i=0;i<s->nb_streams;i++) { + enc = &s->streams[i]->codec; + if (enc->codec_type != CODEC_TYPE_AUDIO) + video_enc = enc; + } + + if (!video_enc) { + av_free(gif); + return -1; + } else { + width = video_enc->width; + height = video_enc->height; + rate = video_enc->frame_rate; + } + + /* XXX: is it allowed ? seems to work so far... */ + video_enc->pix_fmt = PIX_FMT_RGB24; + + /* GIF header */ + + put_tag(pb, "GIF"); + put_tag(pb, "89a"); + put_le16(pb, width); + put_le16(pb, height); + + put_byte(pb, 0xf7); /* flags: global clut, 256 entries */ + put_byte(pb, 0x1f); /* background color index */ + put_byte(pb, 0); /* aspect ratio */ + + /* the global palette */ + + put_buffer(pb, (unsigned char *)gif_clut, 216*3); + for(i=0;i<((256-216)*3);i++) + put_byte(pb, 0); + + /* application extension header */ + /* XXX: not really sure what to put in here... */ +#ifdef GIF_ADD_APP_HEADER + put_byte(pb, 0x21); + put_byte(pb, 0xff); + put_byte(pb, 0x0b); + put_tag(pb, "NETSCAPE2.0"); + put_byte(pb, 0x03); + put_byte(pb, 0x01); + put_byte(pb, 0x00); + put_byte(pb, 0x00); +#endif + + put_flush_packet(&s->pb); + return 0; +} + +/* this is maybe slow, but allows for extensions */ +static inline unsigned char gif_clut_index(rgb_triplet *clut, UINT8 r, UINT8 g, UINT8 b) +{ + return ((((r)/47)%6)*6*6+(((g)/47)%6)*6+(((b)/47)%6)); +} + +/* chunk writer callback */ +/* !!! XXX:deprecated +static void gif_put_chunk(void *pbctx, UINT8 *buffer, int count) +{ + ByteIOContext *pb = (ByteIOContext *)pbctx; + put_byte(pb, (UINT8)count); + put_buffer(pb, buffer, count); +} +*/ + +static int gif_write_video(AVFormatContext *s, + AVCodecContext *enc, UINT8 *buf, int size) +{ + ByteIOContext *pb = &s->pb; + GIFContext *gif = s->priv_data; + int i, left, jiffies; + INT64 delay; + PutBitContext p; + UINT8 buffer[200]; /* 100 * 9 / 8 = 113 */ + + + /* graphic control extension block */ + put_byte(pb, 0x21); + put_byte(pb, 0xf9); + put_byte(pb, 0x04); /* block size */ + put_byte(pb, 0x04); /* flags */ + + /* 1 jiffy is 1/70 s */ + /* the delay_time field indicates the number of jiffies - 1 */ + delay = gif->file_time - gif->time; + + /* XXX: should use delay, in order to be more accurate */ + /* instead of using the same rounded value each time */ + /* XXX: don't even remember if I really use it for now */ + jiffies = (70*FRAME_RATE_BASE/enc->frame_rate) - 1; + + put_le16(pb, jiffies); + + put_byte(pb, 0x1f); /* transparent color index */ + put_byte(pb, 0x00); + + /* image block */ + + put_byte(pb, 0x2c); + put_le16(pb, 0); + put_le16(pb, 0); + put_le16(pb, enc->width); + put_le16(pb, enc->height); + put_byte(pb, 0x00); /* flags */ + /* no local clut */ + + put_byte(pb, 0x08); + + left=size/3; + + init_put_bits(&p, buffer, 130, NULL, NULL); + +/* + * the thing here is the bitstream is written as little packets, with a size byte before + * but it's still the same bitstream between packets (no flush !) + */ + + while(left>0) { + + gif_put_bits_rev(&p, 9, 0x0100); /* clear code */ + + for(i=0;i<GIF_CHUNKS;i++) { + gif_put_bits_rev(&p, 9, gif_clut_index(NULL, *buf, buf[1], buf[2])); + buf+=3; + } + + if(left<=GIF_CHUNKS) { + gif_put_bits_rev(&p, 9, 0x101); /* end of stream */ + gif_flush_put_bits_rev(&p); + } + if(pbBufPtr(&p) - p.buf > 0) { + put_byte(pb, pbBufPtr(&p) - p.buf); /* byte count of the packet */ + put_buffer(pb, p.buf, pbBufPtr(&p) - p.buf); /* the actual buffer */ + p.data_out_size += pbBufPtr(&p) - p.buf; + p.buf_ptr = p.buf; /* dequeue the bytes off the bitstream */ + } + if(left<=GIF_CHUNKS) { + put_byte(pb, 0x00); /* end of image block */ + } + + left-=GIF_CHUNKS; + } + + put_flush_packet(&s->pb); + return 0; +} + +static int gif_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AVCodecContext *codec = &s->streams[stream_index]->codec; + if (codec->codec_type == CODEC_TYPE_AUDIO) + return 0; /* just ignore audio */ + else + return gif_write_video(s, codec, buf, size); +} + +static int gif_write_trailer(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + + put_byte(pb, 0x3b); + put_flush_packet(&s->pb); + return 0; +} + +static AVOutputFormat gif_oformat = { + "gif", + "GIF Animation", + "image/gif", + "gif", + sizeof(GIFContext), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + gif_write_header, + gif_write_packet, + gif_write_trailer, +}; + +int gif_init(void) +{ + av_register_output_format(&gif_oformat); + return 0; +} diff --git a/libavformat/grab.c b/libavformat/grab.c new file mode 100644 index 0000000000..8173bac369 --- /dev/null +++ b/libavformat/grab.c @@ -0,0 +1,829 @@ +/* + * Linux video grab interface + * Copyright (c) 2000,2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <linux/videodev.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <time.h> + +typedef struct { + int fd; + int frame_format; /* see VIDEO_PALETTE_xxx */ + int use_mmap; + int width, height; + int frame_rate; + INT64 time_frame; + int frame_size; + struct video_capability video_cap; + struct video_audio audio_saved; + UINT8 *video_buf; + struct video_mbuf gb_buffers; + struct video_mmap gb_buf; + int gb_frame; + + /* ATI All In Wonder specific stuff */ + /* XXX: remove and merge in libavcodec/imgconvert.c */ + int aiw_enabled; + int deint; + int halfw; + UINT8 *src_mem; + UINT8 *lum_m4_mem; +} VideoData; + +static int aiw_init(VideoData *s); +static int aiw_read_picture(VideoData *s, uint8_t *data); +static int aiw_close(VideoData *s); + +const char *v4l_device = "/dev/video"; + +static int grab_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + VideoData *s = s1->priv_data; + AVStream *st; + int width, height; + int video_fd, frame_size; + int ret, frame_rate; + int desired_palette; + struct video_audio audio; + + if (!ap || ap->width <= 0 || ap->height <= 0 || ap->frame_rate <= 0) + return -1; + + width = ap->width; + height = ap->height; + frame_rate = ap->frame_rate; + + st = av_new_stream(s1, 0); + if (!st) + return -ENOMEM; + + s->width = width; + s->height = height; + s->frame_rate = frame_rate; + + video_fd = open(v4l_device, O_RDWR); + if (video_fd < 0) { + perror(v4l_device); + goto fail; + } + + if (ioctl(video_fd,VIDIOCGCAP, &s->video_cap) < 0) { + perror("VIDIOCGCAP"); + goto fail; + } + + if (!(s->video_cap.type & VID_TYPE_CAPTURE)) { + fprintf(stderr, "Fatal: grab device does not handle capture\n"); + goto fail; + } + + desired_palette = -1; + if (st->codec.pix_fmt == PIX_FMT_YUV420P) { + desired_palette = VIDEO_PALETTE_YUV420P; + } else if (st->codec.pix_fmt == PIX_FMT_YUV422) { + desired_palette = VIDEO_PALETTE_YUV422; + } else if (st->codec.pix_fmt == PIX_FMT_BGR24) { + desired_palette = VIDEO_PALETTE_RGB24; + } + + /* unmute audio */ + audio.audio = 0; + ioctl(video_fd, VIDIOCGAUDIO, &audio); + memcpy(&s->audio_saved, &audio, sizeof(audio)); + audio.flags &= ~VIDEO_AUDIO_MUTE; + ioctl(video_fd, VIDIOCSAUDIO, &audio); + + ret = ioctl(video_fd,VIDIOCGMBUF,&s->gb_buffers); + if (ret < 0) { + /* try to use read based access */ + struct video_window win; + struct video_picture pict; + int val; + + win.x = 0; + win.y = 0; + win.width = width; + win.height = height; + win.chromakey = -1; + win.flags = 0; + + ioctl(video_fd, VIDIOCSWIN, &win); + + ioctl(video_fd, VIDIOCGPICT, &pict); +#if 0 + printf("v4l: colour=%d hue=%d brightness=%d constrast=%d whiteness=%d\n", + pict.colour, + pict.hue, + pict.brightness, + pict.contrast, + pict.whiteness); +#endif + /* try to choose a suitable video format */ + pict.palette = desired_palette; + if (desired_palette == -1 || (ret = ioctl(video_fd, VIDIOCSPICT, &pict)) < 0) { + pict.palette=VIDEO_PALETTE_YUV420P; + ret = ioctl(video_fd, VIDIOCSPICT, &pict); + if (ret < 0) { + pict.palette=VIDEO_PALETTE_YUV422; + ret = ioctl(video_fd, VIDIOCSPICT, &pict); + if (ret < 0) { + pict.palette=VIDEO_PALETTE_RGB24; + ret = ioctl(video_fd, VIDIOCSPICT, &pict); + if (ret < 0) + goto fail1; + } + } + } + + s->frame_format = pict.palette; + + val = 1; + ioctl(video_fd, VIDIOCCAPTURE, &val); + + s->time_frame = av_gettime(); + s->use_mmap = 0; + + /* ATI All In Wonder automatic activation */ + if (!strcmp(s->video_cap.name, "Km")) { + if (aiw_init(s) < 0) + goto fail; + s->aiw_enabled = 1; + /* force 420P format because convertion from YUV422 to YUV420P + is done in this driver (ugly) */ + s->frame_format = VIDEO_PALETTE_YUV420P; + } + } else { + s->video_buf = mmap(0,s->gb_buffers.size,PROT_READ|PROT_WRITE,MAP_SHARED,video_fd,0); + if ((unsigned char*)-1 == s->video_buf) { + perror("mmap"); + goto fail; + } + s->gb_frame = 0; + s->time_frame = av_gettime(); + + /* start to grab the first frame */ + s->gb_buf.frame = s->gb_frame % s->gb_buffers.frames; + s->gb_buf.height = height; + s->gb_buf.width = width; + s->gb_buf.format = desired_palette; + + if (desired_palette == -1 || (ret = ioctl(video_fd, VIDIOCMCAPTURE, &s->gb_buf)) < 0) { + s->gb_buf.format = VIDEO_PALETTE_YUV420P; + + ret = ioctl(video_fd, VIDIOCMCAPTURE, &s->gb_buf); + if (ret < 0 && errno != EAGAIN) { + /* try YUV422 */ + s->gb_buf.format = VIDEO_PALETTE_YUV422; + + ret = ioctl(video_fd, VIDIOCMCAPTURE, &s->gb_buf); + if (ret < 0 && errno != EAGAIN) { + /* try RGB24 */ + s->gb_buf.format = VIDEO_PALETTE_RGB24; + ret = ioctl(video_fd, VIDIOCMCAPTURE, &s->gb_buf); + } + } + } + if (ret < 0) { + if (errno != EAGAIN) { + fail1: + fprintf(stderr, "Fatal: grab device does not support suitable format\n"); + } else { + fprintf(stderr,"Fatal: grab device does not receive any video signal\n"); + } + goto fail; + } + s->frame_format = s->gb_buf.format; + s->use_mmap = 1; + } + + switch(s->frame_format) { + case VIDEO_PALETTE_YUV420P: + frame_size = (width * height * 3) / 2; + st->codec.pix_fmt = PIX_FMT_YUV420P; + break; + case VIDEO_PALETTE_YUV422: + frame_size = width * height * 2; + st->codec.pix_fmt = PIX_FMT_YUV422; + break; + case VIDEO_PALETTE_RGB24: + frame_size = width * height * 3; + st->codec.pix_fmt = PIX_FMT_BGR24; /* NOTE: v4l uses BGR24, not RGB24 ! */ + break; + default: + goto fail; + } + s->fd = video_fd; + s->frame_size = frame_size; + + st->codec.codec_type = CODEC_TYPE_VIDEO; + st->codec.codec_id = CODEC_ID_RAWVIDEO; + st->codec.width = width; + st->codec.height = height; + st->codec.frame_rate = frame_rate; + + av_set_pts_info(s1, 48, 1, 1000000); /* 48 bits pts in us */ + + return 0; + fail: + if (video_fd >= 0) + close(video_fd); + av_free(st); + return -EIO; +} + +static int v4l_mm_read_picture(VideoData *s, UINT8 *buf) +{ + UINT8 *ptr; + + /* Setup to capture the next frame */ + s->gb_buf.frame = (s->gb_frame + 1) % s->gb_buffers.frames; + if (ioctl(s->fd, VIDIOCMCAPTURE, &s->gb_buf) < 0) { + if (errno == EAGAIN) + fprintf(stderr,"Cannot Sync\n"); + else + perror("VIDIOCMCAPTURE"); + return -EIO; + } + + while (ioctl(s->fd, VIDIOCSYNC, &s->gb_frame) < 0 && + (errno == EAGAIN || errno == EINTR)); + + ptr = s->video_buf + s->gb_buffers.offsets[s->gb_frame]; + memcpy(buf, ptr, s->frame_size); + + /* This is now the grabbing frame */ + s->gb_frame = s->gb_buf.frame; + + return s->frame_size; +} + +static int grab_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + VideoData *s = s1->priv_data; + INT64 curtime, delay; + struct timespec ts; + INT64 per_frame = (INT64_C(1000000) * FRAME_RATE_BASE) / s->frame_rate; + + /* Calculate the time of the next frame */ + s->time_frame += per_frame; + + /* wait based on the frame rate */ + for(;;) { + curtime = av_gettime(); + delay = s->time_frame - curtime; + if (delay <= 0) { + if (delay < -per_frame) { + /* printf("grabbing is %d frames late (dropping)\n", (int) -(delay / 16666)); */ + s->time_frame += per_frame; + } + break; + } + ts.tv_sec = delay / 1000000; + ts.tv_nsec = (delay % 1000000) * 1000; + nanosleep(&ts, NULL); + } + + if (av_new_packet(pkt, s->frame_size) < 0) + return -EIO; + + pkt->pts = curtime & ((1LL << 48) - 1); + + /* read one frame */ + if (s->aiw_enabled) { + return aiw_read_picture(s, pkt->data); + } else if (s->use_mmap) { + return v4l_mm_read_picture(s, pkt->data); + } else { + if (read(s->fd, pkt->data, pkt->size) != pkt->size) + return -EIO; + return s->frame_size; + } +} + +static int grab_read_close(AVFormatContext *s1) +{ + VideoData *s = s1->priv_data; + + if (s->aiw_enabled) + aiw_close(s); + + if (s->use_mmap) + munmap(s->video_buf, s->gb_buffers.size); + + /* mute audio. we must force it because the BTTV driver does not + return its state correctly */ + s->audio_saved.flags |= VIDEO_AUDIO_MUTE; + ioctl(s->fd, VIDIOCSAUDIO, &s->audio_saved); + + close(s->fd); + return 0; +} + +static AVInputFormat video_grab_device_format = { + "video_grab_device", + "video grab", + sizeof(VideoData), + NULL, + grab_read_header, + grab_read_packet, + grab_read_close, + .flags = AVFMT_NOFILE, +}; + +/* All in Wonder specific stuff */ +/* XXX: remove and merge in libavcodec/imgconvert.c */ + +static int aiw_init(VideoData *s) +{ + int width, height; + + width = s->width; + height = s->height; + + if ((width == s->video_cap.maxwidth && height == s->video_cap.maxheight) || + (width == s->video_cap.maxwidth && height == s->video_cap.maxheight*2) || + (width == s->video_cap.maxwidth/2 && height == s->video_cap.maxheight)) { + + s->deint=0; + s->halfw=0; + if (height == s->video_cap.maxheight*2) s->deint=1; + if (width == s->video_cap.maxwidth/2) s->halfw=1; + } else { + fprintf(stderr,"\nIncorrect Grab Size Supplied - Supported Sizes Are:\n"); + fprintf(stderr," %dx%d %dx%d %dx%d\n\n", + s->video_cap.maxwidth,s->video_cap.maxheight, + s->video_cap.maxwidth,s->video_cap.maxheight*2, + s->video_cap.maxwidth/2,s->video_cap.maxheight); + goto fail; + } + + if (s->halfw == 0) { + s->src_mem = av_malloc(s->width*2); + } else { + s->src_mem = av_malloc(s->width*4); + } + if (!s->src_mem) goto fail; + + s->lum_m4_mem = av_malloc(s->width); + if (!s->lum_m4_mem) + goto fail; + return 0; + fail: + av_freep(&s->src_mem); + av_freep(&s->lum_m4_mem); + return -1; +} + +#ifdef HAVE_MMX +#include "../libavcodec/i386/mmx.h" + +#define LINE_WITH_UV \ + movq_m2r(ptr[0],mm0); \ + movq_m2r(ptr[8],mm1); \ + movq_r2r(mm0, mm4); \ + punpcklbw_r2r(mm1,mm0); \ + punpckhbw_r2r(mm1,mm4); \ + movq_r2r(mm0,mm5); \ + punpcklbw_r2r(mm4,mm0); \ + punpckhbw_r2r(mm4,mm5); \ + movq_r2r(mm0,mm1); \ + punpcklbw_r2r(mm5,mm1); \ + movq_r2m(mm1,lum[0]); \ + movq_m2r(ptr[16],mm2); \ + movq_m2r(ptr[24],mm1); \ + movq_r2r(mm2,mm4); \ + punpcklbw_r2r(mm1,mm2); \ + punpckhbw_r2r(mm1,mm4); \ + movq_r2r(mm2,mm3); \ + punpcklbw_r2r(mm4,mm2); \ + punpckhbw_r2r(mm4,mm3); \ + movq_r2r(mm2,mm1); \ + punpcklbw_r2r(mm3,mm1); \ + movq_r2m(mm1,lum[8]); \ + punpckhdq_r2r(mm2,mm0); \ + punpckhdq_r2r(mm3,mm5); \ + movq_r2m(mm0,cb[0]); \ + movq_r2m(mm5,cr[0]); + +#define LINE_NO_UV \ + movq_m2r(ptr[0],mm0);\ + movq_m2r(ptr[8],mm1);\ + movq_r2r(mm0, mm4);\ + punpcklbw_r2r(mm1,mm0); \ + punpckhbw_r2r(mm1,mm4);\ + movq_r2r(mm0,mm5);\ + punpcklbw_r2r(mm4,mm0);\ + punpckhbw_r2r(mm4,mm5);\ + movq_r2r(mm0,mm1);\ + punpcklbw_r2r(mm5,mm1);\ + movq_r2m(mm1,lum[0]);\ + movq_m2r(ptr[16],mm2);\ + movq_m2r(ptr[24],mm1);\ + movq_r2r(mm2,mm4);\ + punpcklbw_r2r(mm1,mm2);\ + punpckhbw_r2r(mm1,mm4);\ + movq_r2r(mm2,mm3);\ + punpcklbw_r2r(mm4,mm2);\ + punpckhbw_r2r(mm4,mm3);\ + movq_r2r(mm2,mm1);\ + punpcklbw_r2r(mm3,mm1);\ + movq_r2m(mm1,lum[8]); + +#define LINE_WITHUV_AVG \ + movq_m2r(ptr[0], mm0);\ + movq_m2r(ptr[8], mm1);\ + movq_r2r(mm0, mm4);\ + punpcklbw_r2r(mm1,mm0);\ + punpckhbw_r2r(mm1,mm4);\ + movq_r2r(mm0,mm5);\ + punpcklbw_r2r(mm4,mm0);\ + punpckhbw_r2r(mm4,mm5);\ + movq_r2r(mm0,mm1);\ + movq_r2r(mm5,mm2);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + paddw_r2r(mm6,mm1);\ + paddw_r2r(mm2,mm1);\ + psraw_i2r(1,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,lum[0]);\ + movq_m2r(ptr[16],mm2);\ + movq_m2r(ptr[24],mm1);\ + movq_r2r(mm2,mm4);\ + punpcklbw_r2r(mm1,mm2);\ + punpckhbw_r2r(mm1,mm4);\ + movq_r2r(mm2,mm3);\ + punpcklbw_r2r(mm4,mm2);\ + punpckhbw_r2r(mm4,mm3);\ + movq_r2r(mm2,mm1);\ + movq_r2r(mm3,mm4);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm4);\ + paddw_r2r(mm6,mm1);\ + paddw_r2r(mm4,mm1);\ + psraw_i2r(1,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,lum[4]);\ + punpckhbw_r2r(mm7,mm0);\ + punpckhbw_r2r(mm7,mm2);\ + paddw_r2r(mm6,mm0);\ + paddw_r2r(mm2,mm0);\ + psraw_i2r(1,mm0);\ + packuswb_r2r(mm7,mm0);\ + punpckhbw_r2r(mm7,mm5);\ + punpckhbw_r2r(mm7,mm3);\ + paddw_r2r(mm6,mm5);\ + paddw_r2r(mm3,mm5);\ + psraw_i2r(1,mm5);\ + packuswb_r2r(mm7,mm5);\ + movd_r2m(mm0,cb[0]);\ + movd_r2m(mm5,cr[0]); + +#define LINE_NOUV_AVG \ + movq_m2r(ptr[0],mm0);\ + movq_m2r(ptr[8],mm1);\ + pand_r2r(mm5,mm0);\ + pand_r2r(mm5,mm1);\ + pmaddwd_r2r(mm6,mm0);\ + pmaddwd_r2r(mm6,mm1);\ + packssdw_r2r(mm1,mm0);\ + paddw_r2r(mm6,mm0);\ + psraw_i2r(1,mm0);\ + movq_m2r(ptr[16],mm2);\ + movq_m2r(ptr[24],mm3);\ + pand_r2r(mm5,mm2);\ + pand_r2r(mm5,mm3);\ + pmaddwd_r2r(mm6,mm2);\ + pmaddwd_r2r(mm6,mm3);\ + packssdw_r2r(mm3,mm2);\ + paddw_r2r(mm6,mm2);\ + psraw_i2r(1,mm2);\ + packuswb_r2r(mm2,mm0);\ + movq_r2m(mm0,lum[0]); + +#define DEINT_LINE_LUM(ptroff) \ + movd_m2r(lum_m4[(ptroff)],mm0);\ + movd_m2r(lum_m3[(ptroff)],mm1);\ + movd_m2r(lum_m2[(ptroff)],mm2);\ + movd_m2r(lum_m1[(ptroff)],mm3);\ + movd_m2r(lum[(ptroff)],mm4);\ + punpcklbw_r2r(mm7,mm0);\ + movd_r2m(mm2,lum_m4[(ptroff)]);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + punpcklbw_r2r(mm7,mm3);\ + punpcklbw_r2r(mm7,mm4);\ + psllw_i2r(2,mm1);\ + psllw_i2r(1,mm2);\ + paddw_r2r(mm6,mm1);\ + psllw_i2r(2,mm3);\ + paddw_r2r(mm2,mm1);\ + paddw_r2r(mm4,mm0);\ + paddw_r2r(mm3,mm1);\ + psubusw_r2r(mm0,mm1);\ + psrlw_i2r(3,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,lum_m2[(ptroff)]); + +#else +#include "../libavcodec/dsputil.h" + +#define LINE_WITH_UV \ + lum[0]=ptr[0];lum[1]=ptr[2];lum[2]=ptr[4];lum[3]=ptr[6];\ + cb[0]=ptr[1];cb[1]=ptr[5];\ + cr[0]=ptr[3];cr[1]=ptr[7];\ + lum[4]=ptr[8];lum[5]=ptr[10];lum[6]=ptr[12];lum[7]=ptr[14];\ + cb[2]=ptr[9];cb[3]=ptr[13];\ + cr[2]=ptr[11];cr[3]=ptr[15];\ + lum[8]=ptr[16];lum[9]=ptr[18];lum[10]=ptr[20];lum[11]=ptr[22];\ + cb[4]=ptr[17];cb[5]=ptr[21];\ + cr[4]=ptr[19];cr[5]=ptr[23];\ + lum[12]=ptr[24];lum[13]=ptr[26];lum[14]=ptr[28];lum[15]=ptr[30];\ + cb[6]=ptr[25];cb[7]=ptr[29];\ + cr[6]=ptr[27];cr[7]=ptr[31]; + +#define LINE_NO_UV \ + lum[0]=ptr[0];lum[1]=ptr[2];lum[2]=ptr[4];lum[3]=ptr[6];\ + lum[4]=ptr[8];lum[5]=ptr[10];lum[6]=ptr[12];lum[7]=ptr[14];\ + lum[8]=ptr[16];lum[9]=ptr[18];lum[10]=ptr[20];lum[11]=ptr[22];\ + lum[12]=ptr[24];lum[13]=ptr[26];lum[14]=ptr[28];lum[15]=ptr[30]; + +#define LINE_WITHUV_AVG \ + sum=(ptr[0]+ptr[2]+1) >> 1;lum[0]=sum; \ + sum=(ptr[4]+ptr[6]+1) >> 1;lum[1]=sum; \ + sum=(ptr[1]+ptr[5]+1) >> 1;cb[0]=sum; \ + sum=(ptr[3]+ptr[7]+1) >> 1;cr[0]=sum; \ + sum=(ptr[8]+ptr[10]+1) >> 1;lum[2]=sum; \ + sum=(ptr[12]+ptr[14]+1) >> 1;lum[3]=sum; \ + sum=(ptr[9]+ptr[13]+1) >> 1;cb[1]=sum; \ + sum=(ptr[11]+ptr[15]+1) >> 1;cr[1]=sum; \ + sum=(ptr[16]+ptr[18]+1) >> 1;lum[4]=sum; \ + sum=(ptr[20]+ptr[22]+1) >> 1;lum[5]=sum; \ + sum=(ptr[17]+ptr[21]+1) >> 1;cb[2]=sum; \ + sum=(ptr[19]+ptr[23]+1) >> 1;cr[2]=sum; \ + sum=(ptr[24]+ptr[26]+1) >> 1;lum[6]=sum; \ + sum=(ptr[28]+ptr[30]+1) >> 1;lum[7]=sum; \ + sum=(ptr[25]+ptr[29]+1) >> 1;cb[3]=sum; \ + sum=(ptr[27]+ptr[31]+1) >> 1;cr[3]=sum; + +#define LINE_NOUV_AVG \ + sum=(ptr[0]+ptr[2]+1) >> 1;lum[0]=sum; \ + sum=(ptr[4]+ptr[6]+1) >> 1;lum[1]=sum; \ + sum=(ptr[8]+ptr[10]+1) >> 1;lum[2]=sum; \ + sum=(ptr[12]+ptr[14]+1) >> 1;lum[3]=sum; \ + sum=(ptr[16]+ptr[18]+1) >> 1;lum[4]=sum; \ + sum=(ptr[20]+ptr[22]+1) >> 1;lum[5]=sum; \ + sum=(ptr[24]+ptr[26]+1) >> 1;lum[6]=sum; \ + sum=(ptr[28]+ptr[30]+1) >> 1;lum[7]=sum; + +#define DEINT_LINE_LUM(ptroff) \ + sum=(-lum_m4[(ptroff)]+(lum_m3[(ptroff)]<<2)+(lum_m2[(ptroff)]<<1)+(lum_m1[(ptroff)]<<2)-lum[(ptroff)]); \ + lum_m4[(ptroff)]=lum_m2[(ptroff)];\ + lum_m2[(ptroff)]=cm[(sum+4)>>3];\ + sum=(-lum_m4[(ptroff)+1]+(lum_m3[(ptroff)+1]<<2)+(lum_m2[(ptroff)+1]<<1)+(lum_m1[(ptroff)+1]<<2)-lum[(ptroff)+1]); \ + lum_m4[(ptroff)+1]=lum_m2[(ptroff)+1];\ + lum_m2[(ptroff)+1]=cm[(sum+4)>>3];\ + sum=(-lum_m4[(ptroff)+2]+(lum_m3[(ptroff)+2]<<2)+(lum_m2[(ptroff)+2]<<1)+(lum_m1[(ptroff)+2]<<2)-lum[(ptroff)+2]); \ + lum_m4[(ptroff)+2]=lum_m2[(ptroff)+2];\ + lum_m2[(ptroff)+2]=cm[(sum+4)>>3];\ + sum=(-lum_m4[(ptroff)+3]+(lum_m3[(ptroff)+3]<<2)+(lum_m2[(ptroff)+3]<<1)+(lum_m1[(ptroff)+3]<<2)-lum[(ptroff)+3]); \ + lum_m4[(ptroff)+3]=lum_m2[(ptroff)+3];\ + lum_m2[(ptroff)+3]=cm[(sum+4)>>3]; + +#endif + + +/* Read two fields separately. */ +static int aiw_read_picture(VideoData *s, uint8_t *data) +{ + UINT8 *ptr, *lum, *cb, *cr; + int h; +#ifndef HAVE_MMX + int sum; +#endif + UINT8* src = s->src_mem; + UINT8 *ptrend = &src[s->width*2]; + lum=data; + cb=&lum[s->width*s->height]; + cr=&cb[(s->width*s->height)/4]; + if (s->deint == 0 && s->halfw == 0) { + while (read(s->fd,src,s->width*2) < 0) { + usleep(100); + } + for (h = 0; h < s->height-2; h+=2) { + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + read(s->fd,src,s->width*2); + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16) { + LINE_NO_UV + } + read(s->fd,src,s->width*2); + } + /* + * Do last two lines + */ + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + read(s->fd,src,s->width*2); + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16) { + LINE_NO_UV + } + /* drop second field */ + while (read(s->fd,src,s->width*2) < 0) { + usleep(100); + } + for (h = 0; h < s->height - 1; h++) { + read(s->fd,src,s->width*2); + } + } else if (s->halfw == 1) { +#ifdef HAVE_MMX + mmx_t rounder; + mmx_t masker; + rounder.uw[0]=1; + rounder.uw[1]=1; + rounder.uw[2]=1; + rounder.uw[3]=1; + masker.ub[0]=0xff; + masker.ub[1]=0; + masker.ub[2]=0xff; + masker.ub[3]=0; + masker.ub[4]=0xff; + masker.ub[5]=0; + masker.ub[6]=0xff; + masker.ub[7]=0; + pxor_r2r(mm7,mm7); + movq_m2r(rounder,mm6); +#endif + while (read(s->fd,src,s->width*4) < 0) { + usleep(100); + } + ptrend = &src[s->width*4]; + for (h = 0; h < s->height-2; h+=2) { + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=8, cb+=4, cr+=4) { + LINE_WITHUV_AVG + } + read(s->fd,src,s->width*4); +#ifdef HAVE_MMX + movq_m2r(masker,mm5); +#endif + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=8) { + LINE_NOUV_AVG + } + read(s->fd,src,s->width*4); + } + /* + * Do last two lines + */ + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=8, cb+=4, cr+=4) { + LINE_WITHUV_AVG + } + read(s->fd,src,s->width*4); +#ifdef HAVE_MMX + movq_m2r(masker,mm5); +#endif + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=8) { + LINE_NOUV_AVG + } + /* drop second field */ + while (read(s->fd,src,s->width*4) < 0) { + usleep(100); + } + for (h = 0; h < s->height - 1; h++) { + read(s->fd,src,s->width*4); + } + } else { + UINT8 *lum_m1, *lum_m2, *lum_m3, *lum_m4; +#ifdef HAVE_MMX + mmx_t rounder; + rounder.uw[0]=4; + rounder.uw[1]=4; + rounder.uw[2]=4; + rounder.uw[3]=4; + movq_m2r(rounder,mm6); + pxor_r2r(mm7,mm7); +#else + UINT8 *cm = cropTbl + MAX_NEG_CROP; +#endif + + /* read two fields and deinterlace them */ + while (read(s->fd,src,s->width*2) < 0) { + usleep(100); + } + for (h = 0; h < (s->height/2)-2; h+=2) { + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + read(s->fd,src,s->width*2); + /* skip a luminance line - will be filled in later */ + lum += s->width; + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + /* skip a luminance line - will be filled in later */ + lum += s->width; + read(s->fd,src,s->width*2); + } + /* + * Do last two lines + */ + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + /* skip a luminance line - will be filled in later */ + lum += s->width; + read(s->fd,src,s->width*2); + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, cb+=8, cr+=8) { + LINE_WITH_UV + } + /* + * + * SECOND FIELD + * + */ + lum=&data[s->width]; + while (read(s->fd,src,s->width*2) < 0) { + usleep(10); + } + /* First (and last) two lines not interlaced */ + for (h = 0; h < 2; h++) { + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16) { + LINE_NO_UV + } + read(s->fd,src,s->width*2); + /* skip a luminance line */ + lum += s->width; + } + lum_m1=&lum[-s->width]; + lum_m2=&lum_m1[-s->width]; + lum_m3=&lum_m2[-s->width]; + memmove(s->lum_m4_mem,&lum_m3[-s->width],s->width); + for (; h < (s->height/2)-1; h++) { + lum_m4=s->lum_m4_mem; + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16,lum_m1+=16,lum_m2+=16,lum_m3+=16,lum_m4+=16) { + LINE_NO_UV + + DEINT_LINE_LUM(0) + DEINT_LINE_LUM(4) + DEINT_LINE_LUM(8) + DEINT_LINE_LUM(12) + } + read(s->fd,src,s->width*2); + /* skip a luminance line */ + lum += s->width; + lum_m1 += s->width; + lum_m2 += s->width; + lum_m3 += s->width; + // lum_m4 += s->width; + } + /* + * Do last line + */ + lum_m4=s->lum_m4_mem; + for (ptr = &src[0]; ptr < ptrend; ptr+=32, lum+=16, lum_m1+=16, lum_m2+=16, lum_m3+=16, lum_m4+=16) { + LINE_NO_UV + + DEINT_LINE_LUM(0) + DEINT_LINE_LUM(4) + DEINT_LINE_LUM(8) + DEINT_LINE_LUM(12) + } + } +#ifdef HAVE_MMX + emms(); +#endif + return s->frame_size; +} + +static int aiw_close(VideoData *s) +{ + av_freep(&s->lum_m4_mem); + av_freep(&s->src_mem); + return 0; +} + +int video_grab_init(void) +{ + av_register_input_format(&video_grab_device_format); + return 0; +} diff --git a/libavformat/http.c b/libavformat/http.c new file mode 100644 index 0000000000..7271a6da81 --- /dev/null +++ b/libavformat/http.c @@ -0,0 +1,290 @@ +/* + * HTTP protocol for ffmpeg client + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <unistd.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif +#include <netdb.h> + + +/* XXX: POST protocol is not completly implemented because ffmpeg use + only a subset of it */ + +//#define DEBUG + +/* used for protocol handling */ +#define BUFFER_SIZE 1024 +#define URL_SIZE 4096 + +typedef struct { + URLContext *hd; + unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; + int line_count; + int http_code; + char location[URL_SIZE]; +} HTTPContext; + +static int http_connect(URLContext *h, const char *path, const char *hoststr); +static int http_write(URLContext *h, UINT8 *buf, int size); + + +/* return non zero if error */ +static int http_open(URLContext *h, const char *uri, int flags) +{ + const char *path, *proxy_path; + char hostname[1024], hoststr[1024]; + char path1[1024]; + char buf[1024]; + int port, use_proxy, err; + HTTPContext *s; + URLContext *hd = NULL; + + h->is_streamed = 1; + + s = av_malloc(sizeof(HTTPContext)); + if (!s) { + return -ENOMEM; + } + h->priv_data = s; + + proxy_path = getenv("http_proxy"); + use_proxy = (proxy_path != NULL) && !getenv("no_proxy") && + strstart(proxy_path, "http://", NULL); + + /* fill the dest addr */ + redo: + /* needed in any case to build the host string */ + url_split(NULL, 0, hostname, sizeof(hostname), &port, + path1, sizeof(path1), uri); + if (port > 0) { + snprintf(hoststr, sizeof(hoststr), "%s:%d", hostname, port); + } else { + pstrcpy(hoststr, sizeof(hoststr), hostname); + } + + if (use_proxy) { + url_split(NULL, 0, hostname, sizeof(hostname), &port, + NULL, 0, proxy_path); + path = uri; + } else { + if (path1[0] == '\0') + path = "/"; + else + path = path1; + } + if (port < 0) + port = 80; + + snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port); + err = url_open(&hd, buf, URL_RDWR); + if (err < 0) + goto fail; + + s->hd = hd; + if (http_connect(h, path, hoststr) < 0) + goto fail; + if (s->http_code == 303 && s->location[0] != '\0') { + /* url moved, get next */ + uri = s->location; + url_close(hd); + goto redo; + } + return 0; + fail: + if (hd) + url_close(hd); + av_free(s); + return -EIO; +} + +static int http_getc(HTTPContext *s) +{ + int len; + if (s->buf_ptr >= s->buf_end) { + len = url_read(s->hd, s->buffer, BUFFER_SIZE); + if (len < 0) { + return -EIO; + } else if (len == 0) { + return -1; + } else { + s->buf_ptr = s->buffer; + s->buf_end = s->buffer + len; + } + } + return *s->buf_ptr++; +} + +static int process_line(HTTPContext *s, char *line, int line_count) +{ + char *tag, *p; + + /* end of header */ + if (line[0] == '\0') + return 0; + + p = line; + if (line_count == 0) { + while (!isspace(*p) && *p != '\0') + p++; + while (isspace(*p)) + p++; + s->http_code = strtol(p, NULL, 10); +#ifdef DEBUG + printf("http_code=%d\n", s->http_code); +#endif + } else { + while (*p != '\0' && *p != ':') + p++; + if (*p != ':') + return 1; + + *p = '\0'; + tag = line; + p++; + while (isspace(*p)) + p++; + if (!strcmp(tag, "Location")) { + strcpy(s->location, p); + } + } + return 1; +} + +static int http_connect(URLContext *h, const char *path, const char *hoststr) +{ + HTTPContext *s = h->priv_data; + int post, err, ch; + char line[1024], *q; + + + /* send http header */ + post = h->flags & URL_WRONLY; + + snprintf(s->buffer, sizeof(s->buffer), + "%s %s HTTP/1.0\n" + "User-Agent: FFmpeg %s\n" + "Accept: */*\n" + "Host: %s\n" + "\n", + post ? "POST" : "GET", + path, + FFMPEG_VERSION, + hoststr); + + if (http_write(h, s->buffer, strlen(s->buffer)) < 0) + return -EIO; + + /* init input buffer */ + s->buf_ptr = s->buffer; + s->buf_end = s->buffer; + s->line_count = 0; + s->location[0] = '\0'; + if (post) { + sleep(1); + return 0; + } + + /* wait for header */ + q = line; + for(;;) { + ch = http_getc(s); + if (ch < 0) + return -EIO; + if (ch == '\n') { + /* process line */ + if (q > line && q[-1] == '\r') + q--; + *q = '\0'; +#ifdef DEBUG + printf("header='%s'\n", line); +#endif + err = process_line(s, line, s->line_count); + if (err < 0) + return err; + if (err == 0) + return 0; + s->line_count++; + q = line; + } else { + if ((q - line) < sizeof(line) - 1) + *q++ = ch; + } + } +} + + +static int http_read(URLContext *h, UINT8 *buf, int size) +{ + HTTPContext *s = h->priv_data; + int size1, len; + + size1 = size; + while (size > 0) { + /* read bytes from input buffer first */ + len = s->buf_end - s->buf_ptr; + if (len > 0) { + if (len > size) + len = size; + memcpy(buf, s->buf_ptr, len); + s->buf_ptr += len; + } else { + len = url_read (s->hd, buf, size); + if (len < 0) { + return len; + } else if (len == 0) { + break; + } + } + size -= len; + buf += len; + } + return size1 - size; +} + +/* used only when posting data */ +static int http_write(URLContext *h, UINT8 *buf, int size) +{ + HTTPContext *s = h->priv_data; + return url_write(s->hd, buf, size); +} + +static int http_close(URLContext *h) +{ + HTTPContext *s = h->priv_data; + url_close(s->hd); + av_free(s); + return 0; +} + +URLProtocol http_protocol = { + "http", + http_open, + http_read, + http_write, + NULL, /* seek */ + http_close, +}; + diff --git a/libavformat/img.c b/libavformat/img.c new file mode 100644 index 0000000000..305cbb08f2 --- /dev/null +++ b/libavformat/img.c @@ -0,0 +1,945 @@ +/* + * Image format + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +extern AVInputFormat pgm_iformat; +extern AVOutputFormat pgm_oformat; +extern AVInputFormat pgmyuv_iformat; +extern AVOutputFormat pgmyuv_oformat; +extern AVInputFormat ppm_iformat; +extern AVOutputFormat ppm_oformat; +extern AVInputFormat imgyuv_iformat; +extern AVOutputFormat imgyuv_oformat; +extern AVInputFormat pgmpipe_iformat; +extern AVOutputFormat pgmpipe_oformat; +extern AVInputFormat pgmyuvpipe_iformat; +extern AVOutputFormat pgmyuvpipe_oformat; +extern AVInputFormat ppmpipe_iformat; +extern AVOutputFormat ppmpipe_oformat; +extern AVOutputFormat yuv4mpegpipe_oformat; + +#define IMGFMT_YUV 1 +#define IMGFMT_PGMYUV 2 +#define IMGFMT_PGM 3 +#define IMGFMT_PPM 4 +#define IMGFMT_YUV4MPEG 5 + +#define Y4M_MAGIC "YUV4MPEG2" +#define Y4M_FRAME_MAGIC "FRAME" +#define Y4M_LINE_MAX 256 + +typedef struct { + int width; + int height; + int img_number; + int img_size; + int img_fmt; + int is_pipe; + int header_written; + char path[1024]; +} VideoData; + +static inline int pnm_space(int c) +{ + return (c==' ' || c=='\n' || c=='\r' || c=='\t'); +} + +static void pnm_get(ByteIOContext *f, char *str, int buf_size) +{ + char *s; + int c; + + do { + c=get_byte(f); + if (c=='#') { + do { + c=get_byte(f); + } while (c!='\n'); + c=get_byte(f); + } + } while (pnm_space(c)); + + s=str; + do { + if (url_feof(f)) + break; + if ((s - str) < buf_size - 1) + *s++=c; + c=get_byte(f); + } while (!pnm_space(c)); + *s = '\0'; +} + +static int pgm_read(VideoData *s, ByteIOContext *f, UINT8 *buf, int size, int is_yuv) +{ + int width, height, i; + char buf1[32]; + UINT8 *picture[3]; + + width = s->width; + height = s->height; + + pnm_get(f, buf1, sizeof(buf1)); + if (strcmp(buf1, "P5")) { + return -EIO; + } + pnm_get(f, buf1, sizeof(buf1)); + pnm_get(f, buf1, sizeof(buf1)); + pnm_get(f, buf1, sizeof(buf1)); + + picture[0] = buf; + picture[1] = buf + width * height; + picture[2] = buf + width * height + (width * height / 4); + get_buffer(f, picture[0], width * height); + + height>>=1; + width>>=1; + if (is_yuv) { + for(i=0;i<height;i++) { + get_buffer(f, picture[1] + i * width, width); + get_buffer(f, picture[2] + i * width, width); + } + } else { + for(i=0;i<height;i++) { + memset(picture[1] + i * width, 128, width); + memset(picture[2] + i * width, 128, width); + } + } + return 0; +} + +static int ppm_read(VideoData *s, ByteIOContext *f, UINT8 *buf, int size) +{ + int width, height; + char buf1[32]; + UINT8 *picture[3]; + + width = s->width; + height = s->height; + + pnm_get(f, buf1, sizeof(buf1)); + if (strcmp(buf1, "P6")) { + return -EIO; + } + + pnm_get(f, buf1, sizeof(buf1)); + pnm_get(f, buf1, sizeof(buf1)); + pnm_get(f, buf1, sizeof(buf1)); + + picture[0] = buf; + get_buffer(f, picture[0], width * height*3); + + return 0; + +} + +static int yuv_read(VideoData *s, const char *filename, UINT8 *buf, int size1) +{ + ByteIOContext pb1, *pb = &pb1; + char fname[1024], *p; + int size; + + size = s->width * s->height; + + strcpy(fname, filename); + p = strrchr(fname, '.'); + if (!p || p[1] != 'Y') + return -EIO; + + if (url_fopen(pb, fname, URL_RDONLY) < 0) + return -EIO; + + get_buffer(pb, buf, size); + url_fclose(pb); + + p[1] = 'U'; + if (url_fopen(pb, fname, URL_RDONLY) < 0) + return -EIO; + + get_buffer(pb, buf + size, size / 4); + url_fclose(pb); + + p[1] = 'V'; + if (url_fopen(pb, fname, URL_RDONLY) < 0) + return -EIO; + + get_buffer(pb, buf + size + (size / 4), size / 4); + url_fclose(pb); + return 0; +} + +static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + VideoData *s = s1->priv_data; + char filename[1024]; + int ret; + ByteIOContext f1, *f; + +/* + This if-statement destroys pipes - I do not see why it is necessary + if (get_frame_filename(filename, sizeof(filename), + s->path, s->img_number) < 0) + return -EIO; +*/ + get_frame_filename(filename, sizeof(filename), + s->path, s->img_number); + if (!s->is_pipe) { + f = &f1; + if (url_fopen(f, filename, URL_RDONLY) < 0) + return -EIO; + } else { + f = &s1->pb; + if (url_feof(f)) + return -EIO; + } + + av_new_packet(pkt, s->img_size); + pkt->stream_index = 0; + + switch(s->img_fmt) { + case IMGFMT_PGMYUV: + ret = pgm_read(s, f, pkt->data, pkt->size, 1); + break; + case IMGFMT_PGM: + ret = pgm_read(s, f, pkt->data, pkt->size, 0); + break; + case IMGFMT_YUV: + ret = yuv_read(s, filename, pkt->data, pkt->size); + break; + case IMGFMT_PPM: + ret = ppm_read(s, f, pkt->data, pkt->size); + break; + default: + return -EIO; + } + + if (!s->is_pipe) { + url_fclose(f); + } + + if (ret < 0) { + av_free_packet(pkt); + return -EIO; /* signal EOF */ + } else { + pkt->pts = ((INT64)s->img_number * s1->pts_den * FRAME_RATE_BASE) / (s1->streams[0]->codec.frame_rate * s1->pts_num); + s->img_number++; + return 0; + } +} + +static int sizes[][2] = { + { 640, 480 }, + { 720, 480 }, + { 720, 576 }, + { 352, 288 }, + { 352, 240 }, + { 160, 128 }, + { 512, 384 }, + { 640, 352 }, + { 640, 240 }, +}; + +static int infer_size(int *width_ptr, int *height_ptr, int size) +{ + int i; + + for(i=0;i<sizeof(sizes)/sizeof(sizes[0]);i++) { + if ((sizes[i][0] * sizes[i][1]) == size) { + *width_ptr = sizes[i][0]; + *height_ptr = sizes[i][1]; + return 0; + } + } + return -1; +} + +static int img_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + VideoData *s = s1->priv_data; + int i, h; + char buf[1024]; + char buf1[32]; + ByteIOContext pb1, *f = &pb1; + AVStream *st; + + st = av_new_stream(s1, 0); + if (!st) { + av_free(s); + return -ENOMEM; + } + + strcpy(s->path, s1->filename); + s->img_number = 0; + + /* find format */ + if (s1->iformat->flags & AVFMT_NOFILE) + s->is_pipe = 0; + else + s->is_pipe = 1; + + if (s1->iformat == &pgmyuvpipe_iformat || + s1->iformat == &pgmyuv_iformat) + s->img_fmt = IMGFMT_PGMYUV; + else if (s1->iformat == &pgmpipe_iformat || + s1->iformat == &pgm_iformat) + s->img_fmt = IMGFMT_PGM; + else if (s1->iformat == &imgyuv_iformat) + s->img_fmt = IMGFMT_YUV; + else if (s1->iformat == &ppmpipe_iformat || + s1->iformat == &ppm_iformat) + s->img_fmt = IMGFMT_PPM; + else + goto fail; + + if (!s->is_pipe) { + /* try to find the first image */ + for(i=0;i<5;i++) { + if (get_frame_filename(buf, sizeof(buf), s->path, s->img_number) < 0) + goto fail; + if (url_fopen(f, buf, URL_RDONLY) >= 0) + break; + s->img_number++; + } + if (i == 5) + goto fail; + } else { + f = &s1->pb; + } + + /* find the image size */ + /* XXX: use generic file format guessing, as mpeg */ + switch(s->img_fmt) { + case IMGFMT_PGM: + case IMGFMT_PGMYUV: + case IMGFMT_PPM: + pnm_get(f, buf1, sizeof(buf1)); + pnm_get(f, buf1, sizeof(buf1)); + s->width = atoi(buf1); + pnm_get(f, buf1, sizeof(buf1)); + h = atoi(buf1); + if (s->img_fmt == IMGFMT_PGMYUV) + h = (h * 2) / 3; + s->height = h; + if (s->width <= 0 || + s->height <= 0 || + (s->width % 2) != 0 || + (s->height % 2) != 0) { + goto fail1; + } + break; + case IMGFMT_YUV: + /* infer size by using the file size. */ + { + int img_size; + URLContext *h; + + /* XXX: hack hack */ + h = url_fileno(f); + img_size = url_seek(h, 0, SEEK_END); + if (infer_size(&s->width, &s->height, img_size) < 0) { + goto fail1; + } + } + break; + } + + + if (!s->is_pipe) { + url_fclose(f); + } else { + url_fseek(f, 0, SEEK_SET); + } + + + st->codec.codec_type = CODEC_TYPE_VIDEO; + st->codec.codec_id = CODEC_ID_RAWVIDEO; + st->codec.width = s->width; + st->codec.height = s->height; + if (s->img_fmt == IMGFMT_PPM) { + st->codec.pix_fmt = PIX_FMT_RGB24; + s->img_size = (s->width * s->height * 3); + } else { + st->codec.pix_fmt = PIX_FMT_YUV420P; + s->img_size = (s->width * s->height * 3) / 2; + } + if (!ap || !ap->frame_rate) + st->codec.frame_rate = 25 * FRAME_RATE_BASE; + else + st->codec.frame_rate = ap->frame_rate; + + return 0; + fail1: + if (!s->is_pipe) + url_fclose(f); + fail: + av_free(s); + return -EIO; +} + +static int img_read_close(AVFormatContext *s1) +{ + return 0; +} + +/******************************************************/ +/* image output */ + +static int pgm_save(AVPicture *picture, int width, int height, ByteIOContext *pb, int is_yuv) +{ + int i, h; + char buf[100]; + UINT8 *ptr, *ptr1, *ptr2; + + h = height; + if (is_yuv) + h = (height * 3) / 2; + snprintf(buf, sizeof(buf), + "P5\n%d %d\n%d\n", + width, h, 255); + put_buffer(pb, buf, strlen(buf)); + + ptr = picture->data[0]; + for(i=0;i<height;i++) { + put_buffer(pb, ptr, width); + ptr += picture->linesize[0]; + } + + if (is_yuv) { + height >>= 1; + width >>= 1; + ptr1 = picture->data[1]; + ptr2 = picture->data[2]; + for(i=0;i<height;i++) { + put_buffer(pb, ptr1, width); + put_buffer(pb, ptr2, width); + ptr1 += picture->linesize[1]; + ptr2 += picture->linesize[2]; + } + } + put_flush_packet(pb); + return 0; +} + +static int ppm_save(AVPicture *picture, int width, int height, ByteIOContext *pb) +{ + int i; + char buf[100]; + UINT8 *ptr; + + snprintf(buf, sizeof(buf), + "P6\n%d %d\n%d\n", + width, height, 255); + put_buffer(pb, buf, strlen(buf)); + + ptr = picture->data[0]; + for(i=0;i<height;i++) { + put_buffer(pb, ptr, width * 3); + ptr += picture->linesize[0]; + } + + put_flush_packet(pb); + return 0; +} + +static int yuv_save(AVPicture *picture, int width, int height, const char *filename) +{ + ByteIOContext pb1, *pb = &pb1; + char fname[1024], *p; + int i, j; + UINT8 *ptr; + static char *ext = "YUV"; + + strcpy(fname, filename); + p = strrchr(fname, '.'); + if (!p || p[1] != 'Y') + return -EIO; + + for(i=0;i<3;i++) { + if (i == 1) { + width >>= 1; + height >>= 1; + } + + p[1] = ext[i]; + if (url_fopen(pb, fname, URL_WRONLY) < 0) + return -EIO; + + ptr = picture->data[i]; + for(j=0;j<height;j++) { + put_buffer(pb, ptr, width); + ptr += picture->linesize[i]; + } + put_flush_packet(pb); + url_fclose(pb); + } + return 0; +} + +static int yuv4mpeg_save(AVPicture *picture, int width, int height, ByteIOContext *pb, int need_stream_header, + int is_yuv, int raten, int rated, int aspectn, int aspectd) +{ + int i, n, m; + char buf[Y4M_LINE_MAX+1], buf1[20]; + UINT8 *ptr, *ptr1, *ptr2; + + /* construct stream header, if this is the first frame */ + if(need_stream_header) { + n = snprintf(buf, sizeof(buf), "%s W%d H%d F%d:%d I%s A%d:%d\n", + Y4M_MAGIC, + width, + height, + raten, rated, + "p", /* ffmpeg seems to only output progressive video */ + aspectn, aspectd); + if (n < 0) { + fprintf(stderr, "Error. YUV4MPEG stream header write failed.\n"); + } else { + fprintf(stderr, "YUV4MPEG stream header written. FPS is %d\n", raten); + put_buffer(pb, buf, strlen(buf)); + } + } + + /* construct frame header */ + m = snprintf(buf1, sizeof(buf1), "%s \n", Y4M_FRAME_MAGIC); + if (m < 0) { + fprintf(stderr, "Error. YUV4MPEG frame header write failed.\n"); + } else { + /* fprintf(stderr, "YUV4MPEG frame header written.\n"); */ + put_buffer(pb, buf1, strlen(buf1)); + } + + ptr = picture->data[0]; + for(i=0;i<height;i++) { + put_buffer(pb, ptr, width); + ptr += picture->linesize[0]; + } + + if (is_yuv) { + height >>= 1; + width >>= 1; + ptr1 = picture->data[1]; + ptr2 = picture->data[2]; + for(i=0;i<height;i++) { /* Cb */ + put_buffer(pb, ptr1, width); + ptr1 += picture->linesize[1]; + } + for(i=0;i<height;i++) { /* Cr */ + put_buffer(pb, ptr2, width); + ptr2 += picture->linesize[2]; + } + } + put_flush_packet(pb); + return 0; +} + +static int img_write_header(AVFormatContext *s) +{ + VideoData *img = s->priv_data; + + img->img_number = 1; + strcpy(img->path, s->filename); + + /* find format */ + if (s->oformat->flags & AVFMT_NOFILE) + img->is_pipe = 0; + else + img->is_pipe = 1; + + if (s->oformat == &pgmyuvpipe_oformat || + s->oformat == &pgmyuv_oformat) { + img->img_fmt = IMGFMT_PGMYUV; + } else if (s->oformat == &pgmpipe_oformat || + s->oformat == &pgm_oformat) { + img->img_fmt = IMGFMT_PGM; + } else if (s->oformat == &imgyuv_oformat) { + img->img_fmt = IMGFMT_YUV; + } else if (s->oformat == &ppmpipe_oformat || + s->oformat == &ppm_oformat) { + img->img_fmt = IMGFMT_PPM; + } else if (s->oformat == &yuv4mpegpipe_oformat) { + img->img_fmt = IMGFMT_YUV4MPEG; + img->header_written = 0; + } else { + goto fail; + } + return 0; + fail: + av_free(img); + return -EIO; +} + +static int img_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + VideoData *img = s->priv_data; + AVStream *st = s->streams[stream_index]; + ByteIOContext pb1, *pb; + AVPicture picture; + int width, height, need_stream_header, ret, size1, raten, rated, aspectn, aspectd, fps, fps1; + char filename[1024]; + + width = st->codec.width; + height = st->codec.height; + + if (img->img_number == 1) { + need_stream_header = 1; + } else { + need_stream_header = 0; + } + + fps = st->codec.frame_rate; + fps1 = (((float)fps / FRAME_RATE_BASE) * 1000); + + /* Sorry about this messy code, but mpeg2enc is very picky about + * the framerates it accepts. */ + switch(fps1) { + case 23976: + raten = 24000; /* turn the framerate into a ratio */ + rated = 1001; + break; + case 29970: + raten = 30000; + rated = 1001; + break; + case 25000: + raten = 25; + rated = 1; + break; + case 30000: + raten = 30; + rated = 1; + break; + case 24000: + raten = 24; + rated = 1; + break; + case 50000: + raten = 50; + rated = 1; + break; + case 59940: + raten = 60000; + rated = 1001; + break; + case 60000: + raten = 60; + rated = 1; + break; + default: + raten = fps1; /* this setting should work, but often doesn't */ + rated = 1000; + break; + } + + aspectn = 1; + aspectd = 1; /* ffmpeg always uses a 1:1 aspect ratio */ + + switch(st->codec.pix_fmt) { + case PIX_FMT_YUV420P: + size1 = (width * height * 3) / 2; + if (size != size1) + return -EIO; + + picture.data[0] = buf; + picture.data[1] = picture.data[0] + width * height; + picture.data[2] = picture.data[1] + (width * height) / 4; + picture.linesize[0] = width; + picture.linesize[1] = width >> 1; + picture.linesize[2] = width >> 1; + break; + case PIX_FMT_RGB24: + size1 = (width * height * 3); + if (size != size1) + return -EIO; + picture.data[0] = buf; + picture.linesize[0] = width * 3; + break; + default: + return -EIO; + } + +/* + This if-statement destroys pipes - I do not see why it is necessary + if (get_frame_filename(filename, sizeof(filename), + img->path, img->img_number) < 0) + return -EIO; +*/ + get_frame_filename(filename, sizeof(filename), + img->path, img->img_number); + if (!img->is_pipe) { + pb = &pb1; + if (url_fopen(pb, filename, URL_WRONLY) < 0) + return -EIO; + } else { + pb = &s->pb; + } + switch(img->img_fmt) { + case IMGFMT_PGMYUV: + ret = pgm_save(&picture, width, height, pb, 1); + break; + case IMGFMT_PGM: + ret = pgm_save(&picture, width, height, pb, 0); + break; + case IMGFMT_YUV: + ret = yuv_save(&picture, width, height, filename); + break; + case IMGFMT_PPM: + ret = ppm_save(&picture, width, height, pb); + break; + case IMGFMT_YUV4MPEG: + ret = yuv4mpeg_save(&picture, width, height, pb, + need_stream_header, 1, raten, rated, aspectn, aspectd); + break; + } + if (!img->is_pipe) { + url_fclose(pb); + } + + img->img_number++; + return 0; +} + +static int img_write_trailer(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat pgm_iformat = { + "pgm", + "pgm image format", + sizeof(VideoData), + NULL, + img_read_header, + img_read_packet, + img_read_close, + NULL, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, + .extensions = "pgm", +}; + +static AVOutputFormat pgm_oformat = { + "pgm", + "pgm image format", + "", + "pgm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, +}; + +static AVInputFormat pgmyuv_iformat = { + "pgmyuv", + "pgm with YUV content image format", + sizeof(VideoData), + NULL, /* no probe */ + img_read_header, + img_read_packet, + img_read_close, + NULL, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, +}; + +static AVOutputFormat pgmyuv_oformat = { + "pgmyuv", + "pgm with YUV content image format", + "", + "pgm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, +}; + +static AVInputFormat ppm_iformat = { + "ppm", + "ppm image format", + sizeof(VideoData), + NULL, + img_read_header, + img_read_packet, + img_read_close, + NULL, + AVFMT_NOFILE | AVFMT_NEEDNUMBER | AVFMT_RGB24, + .extensions = "ppm", +}; + +static AVOutputFormat ppm_oformat = { + "ppm", + "ppm image format", + "", + "ppm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, + AVFMT_NOFILE | AVFMT_NEEDNUMBER | AVFMT_RGB24, +}; + +static AVInputFormat imgyuv_iformat = { + ".Y.U.V", + ".Y.U.V format", + sizeof(VideoData), + NULL, + img_read_header, + img_read_packet, + img_read_close, + NULL, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, + .extensions = "Y", +}; + +static AVOutputFormat imgyuv_oformat = { + ".Y.U.V", + ".Y.U.V format", + "", + "Y", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, + AVFMT_NOFILE | AVFMT_NEEDNUMBER, +}; + +static AVInputFormat pgmpipe_iformat = { + "pgmpipe", + "PGM pipe format", + sizeof(VideoData), + NULL, /* no probe */ + img_read_header, + img_read_packet, + img_read_close, + NULL, +}; + +static AVOutputFormat pgmpipe_oformat = { + "pgmpipe", + "PGM pipe format", + "", + "pgm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, +}; + +static AVInputFormat pgmyuvpipe_iformat = { + "pgmyuvpipe", + "PGM YUV pipe format", + sizeof(VideoData), + NULL, /* no probe */ + img_read_header, + img_read_packet, + img_read_close, + NULL, +}; + +static AVOutputFormat pgmyuvpipe_oformat = { + "pgmyuvpipe", + "PGM YUV pipe format", + "", + "pgm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, +}; + +static AVInputFormat ppmpipe_iformat = { + "ppmpipe", + "PPM pipe format", + sizeof(VideoData), + NULL, /* no probe */ + img_read_header, + img_read_packet, + img_read_close, + NULL, + .flags = AVFMT_RGB24, +}; + +static AVOutputFormat ppmpipe_oformat = { + "ppmpipe", + "PPM pipe format", + "", + "ppm", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, + .flags = AVFMT_RGB24, +}; + + +static AVOutputFormat yuv4mpegpipe_oformat = { + "yuv4mpegpipe", + "YUV4MPEG pipe format", + "", + "yuv4mpeg", + sizeof(VideoData), + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + img_write_header, + img_write_packet, + img_write_trailer, +}; + + +int img_init(void) +{ + av_register_input_format(&pgm_iformat); + av_register_output_format(&pgm_oformat); + + av_register_input_format(&pgmyuv_iformat); + av_register_output_format(&pgmyuv_oformat); + + av_register_input_format(&ppm_iformat); + av_register_output_format(&ppm_oformat); + + av_register_input_format(&imgyuv_iformat); + av_register_output_format(&imgyuv_oformat); + + av_register_input_format(&pgmpipe_iformat); + av_register_output_format(&pgmpipe_oformat); + + av_register_input_format(&pgmyuvpipe_iformat); + av_register_output_format(&pgmyuvpipe_oformat); + + av_register_input_format(&ppmpipe_iformat); + av_register_output_format(&ppmpipe_oformat); + + av_register_output_format(&yuv4mpegpipe_oformat); + + return 0; +} diff --git a/libavformat/jpeg.c b/libavformat/jpeg.c new file mode 100644 index 0000000000..6a19db6d67 --- /dev/null +++ b/libavformat/jpeg.c @@ -0,0 +1,266 @@ +/* + * JPEG based formats + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* Multipart JPEG */ + +#define BOUNDARY_TAG "ffserver" + +static int mpjpeg_write_header(AVFormatContext *s) +{ + UINT8 buf1[256]; + + snprintf(buf1, sizeof(buf1), "--%s\n", BOUNDARY_TAG); + put_buffer(&s->pb, buf1, strlen(buf1)); + put_flush_packet(&s->pb); + return 0; +} + +static int mpjpeg_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + UINT8 buf1[256]; + + snprintf(buf1, sizeof(buf1), "Content-type: image/jpeg\n\n"); + put_buffer(&s->pb, buf1, strlen(buf1)); + put_buffer(&s->pb, buf, size); + + snprintf(buf1, sizeof(buf1), "\n--%s\n", BOUNDARY_TAG); + put_buffer(&s->pb, buf1, strlen(buf1)); + put_flush_packet(&s->pb); + return 0; +} + +static int mpjpeg_write_trailer(AVFormatContext *s) +{ + return 0; +} + +static AVOutputFormat mpjpeg_format = { + "mpjpeg", + "Mime multipart JPEG format", + "multipart/x-mixed-replace;boundary=" BOUNDARY_TAG, + "mjpg", + 0, + CODEC_ID_NONE, + CODEC_ID_MJPEG, + mpjpeg_write_header, + mpjpeg_write_packet, + mpjpeg_write_trailer, +}; + + +/*************************************/ +/* single frame JPEG */ + +static int single_jpeg_write_header(AVFormatContext *s) +{ + return 0; +} + +static int single_jpeg_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + put_buffer(&s->pb, buf, size); + put_flush_packet(&s->pb); + return 1; /* no more data can be sent */ +} + +static int single_jpeg_write_trailer(AVFormatContext *s) +{ + return 0; +} + +static AVOutputFormat single_jpeg_format = { + "singlejpeg", + "single JPEG image", + "image/jpeg", + NULL, /* note: no extension to favorize jpeg multiple images match */ + 0, + CODEC_ID_NONE, + CODEC_ID_MJPEG, + single_jpeg_write_header, + single_jpeg_write_packet, + single_jpeg_write_trailer, +}; + +/*************************************/ +/* multiple jpeg images */ + +typedef struct JpegContext { + char path[1024]; + int img_number; +} JpegContext; + +static int jpeg_write_header(AVFormatContext *s1) +{ + JpegContext *s; + + s = av_mallocz(sizeof(JpegContext)); + if (!s) + return -1; + s1->priv_data = s; + pstrcpy(s->path, sizeof(s->path), s1->filename); + s->img_number = 1; + return 0; +} + +static int jpeg_write_packet(AVFormatContext *s1, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + JpegContext *s = s1->priv_data; + char filename[1024]; + ByteIOContext f1, *pb = &f1; + + if (get_frame_filename(filename, sizeof(filename), + s->path, s->img_number) < 0) + return -EIO; + if (url_fopen(pb, filename, URL_WRONLY) < 0) + return -EIO; + + put_buffer(pb, buf, size); + put_flush_packet(pb); + + url_fclose(pb); + s->img_number++; + + return 0; +} + +static int jpeg_write_trailer(AVFormatContext *s1) +{ + return 0; +} + +/***/ + +static int jpeg_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + JpegContext *s; + int i; + char buf[1024]; + ByteIOContext pb1, *f = &pb1; + AVStream *st; + + s = av_mallocz(sizeof(JpegContext)); + if (!s) + return -1; + s1->priv_data = s; + pstrcpy(s->path, sizeof(s->path), s1->filename); + + s1->nb_streams = 1; + st = av_mallocz(sizeof(AVStream)); + if (!st) { + av_free(s); + return -ENOMEM; + } + s1->streams[0] = st; + s->img_number = 0; + + /* try to find the first image */ + for(i=0;i<5;i++) { + if (get_frame_filename(buf, sizeof(buf), s->path, s->img_number) < 0) + goto fail; + if (url_fopen(f, buf, URL_RDONLY) >= 0) + break; + s->img_number++; + } + if (i == 5) + goto fail; + url_fclose(f); + st->codec.codec_type = CODEC_TYPE_VIDEO; + st->codec.codec_id = CODEC_ID_MJPEG; + + if (!ap || !ap->frame_rate) + st->codec.frame_rate = 25 * FRAME_RATE_BASE; + else + st->codec.frame_rate = ap->frame_rate; + return 0; + fail: + av_free(s); + return -EIO; +} + +static int jpeg_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + JpegContext *s = s1->priv_data; + char filename[1024]; + int size; + ByteIOContext f1, *f = &f1; + + if (get_frame_filename(filename, sizeof(filename), + s->path, s->img_number) < 0) + return -EIO; + + f = &f1; + if (url_fopen(f, filename, URL_RDONLY) < 0) + return -EIO; + + size = url_seek(url_fileno(f), 0, SEEK_END); + url_seek(url_fileno(f), 0, SEEK_SET); + + av_new_packet(pkt, size); + pkt->stream_index = 0; + get_buffer(f, pkt->data, size); + + url_fclose(f); + s->img_number++; + return 0; +} + +static int jpeg_read_close(AVFormatContext *s1) +{ + return 0; +} + +static AVInputFormat jpeg_iformat = { + "jpeg", + "JPEG image", + sizeof(JpegContext), + NULL, + jpeg_read_header, + jpeg_read_packet, + jpeg_read_close, + NULL, + .flags = AVFMT_NOFILE | AVFMT_NEEDNUMBER, + .extensions = "jpg,jpeg", +}; + +static AVOutputFormat jpeg_oformat = { + "jpeg", + "JPEG image", + "image/jpeg", + "jpg,jpeg", + sizeof(JpegContext), + CODEC_ID_NONE, + CODEC_ID_MJPEG, + jpeg_write_header, + jpeg_write_packet, + jpeg_write_trailer, + .flags = AVFMT_NOFILE | AVFMT_NEEDNUMBER, +}; + +int jpeg_init(void) +{ + av_register_output_format(&mpjpeg_format); + av_register_output_format(&single_jpeg_format); + av_register_input_format(&jpeg_iformat); + av_register_output_format(&jpeg_oformat); + return 0; +} diff --git a/libavformat/mov.c b/libavformat/mov.c new file mode 100644 index 0000000000..91c7155b08 --- /dev/null +++ b/libavformat/mov.c @@ -0,0 +1,1347 @@ +/* + * MOV decoder. + * Copyright (c) 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include "avi.h" + +#ifdef CONFIG_ZLIB +#include <zlib.h> +#endif + +/* + * First version by Francois Revol revol@free.fr + * + * Features and limitations: + * - reads most of the QT files I have (at least the structure), + * the exceptions are .mov with zlib compressed headers ('cmov' section). It shouldn't be hard to implement. + * FIXED, Francois Revol, 07/17/2002 + * - ffmpeg has nearly none of the usual QuickTime codecs, + * although I succesfully dumped raw and mp3 audio tracks off .mov files. + * Sample QuickTime files with mp3 audio can be found at: http://www.3ivx.com/showcase.html + * - .mp4 parsing is still hazardous, although the format really is QuickTime with some minor changes + * (to make .mov parser crash maybe ?), despite what they say in the MPEG FAQ at + * http://mpeg.telecomitalialab.com/faq.htm + * - the code is quite ugly... maybe I won't do it recursive next time :-) + * + * Funny I didn't know about http://sourceforge.net/projects/qt-ffmpeg/ + * when coding this :) (it's a writer anyway) + * + * Reference documents: + * http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + * Apple: + * http://developer.apple.com/techpubs/quicktime/qtdevdocs/QTFF/qtff.html + * http://developer.apple.com/techpubs/quicktime/qtdevdocs/PDF/QTFileFormat.pdf + * QuickTime is a trademark of Apple (AFAIK :)) + */ + +//#define DEBUG + +/* allows chunk splitting - should work now... */ +/* in case you can't read a file, try commenting */ +#define MOV_SPLIT_CHUNKS + +#ifdef DEBUG +/* + * XXX: static sux, even more in a multithreaded environment... + * Avoid them. This is here just to help debugging. + */ +static int debug_indent = 0; +void print_atom(const char *str, UINT32 type, UINT64 offset, UINT64 size) +{ + unsigned int tag, i; + tag = (unsigned int) type; + i=debug_indent; + if(tag == 0) tag = MKTAG('N', 'U', 'L', 'L'); + while(i--) + printf("|"); + printf("parse:"); + printf(" %s: tag=%c%c%c%c offset=0x%x size=0x%x\n", + str, tag & 0xff, + (tag >> 8) & 0xff, + (tag >> 16) & 0xff, + (tag >> 24) & 0xff, + (unsigned int)offset, + (unsigned int)size); +} +#endif + +/* some streams in QT (and in MP4 mostly) aren't either video nor audio */ +/* so we first list them as this, then clean up the list of streams we give back, */ +/* getting rid of these */ +#define CODEC_TYPE_MOV_OTHER 2 + +static const CodecTag mov_video_tags[] = { +/* { CODEC_ID_, MKTAG('c', 'v', 'i', 'd') }, *//* Cinepak */ +/* { CODEC_ID_JPEG, MKTAG('j', 'p', 'e', 'g') }, *//* JPEG */ +/* { CODEC_ID_H263, MKTAG('r', 'a', 'w', ' ') }, *//* Uncompressed RGB */ +/* { CODEC_ID_H263, MKTAG('Y', 'u', 'v', '2') }, *//* Uncompressed YUV422 */ +/* Graphics */ +/* Animation */ +/* Apple video */ +/* Kodak Photo CD */ + { CODEC_ID_MJPEG, MKTAG('j', 'p', 'e', 'g') }, /* PhotoJPEG */ + { CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'e', 'g') }, /* MPEG */ + { CODEC_ID_MJPEG, MKTAG('m', 'j', 'p', 'a') }, /* Motion-JPEG (format A) */ + { CODEC_ID_MJPEG, MKTAG('m', 'j', 'p', 'b') }, /* Motion-JPEG (format B) */ +/* { CODEC_ID_GIF, MKTAG('g', 'i', 'f', ' ') }, *//* embedded gif files as frames (usually one "click to play movie" frame) */ +/* Sorenson video */ + { CODEC_ID_SVQ1, MKTAG('S', 'V', 'Q', '1') }, /* Sorenson Video v1 */ + { CODEC_ID_SVQ1, MKTAG('s', 'v', 'q', '1') }, /* Sorenson Video v1 */ + { CODEC_ID_SVQ1, MKTAG('s', 'v', 'q', 'i') }, /* Sorenson Video v1 (from QT specs)*/ + { CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') }, + { CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', 'X') }, /* OpenDiVX *//* sample files at http://heroinewarrior.com/xmovie.php3 use this tag */ +/* { CODEC_ID_, MKTAG('I', 'V', '5', '0') }, *//* Indeo 5.0 */ + { CODEC_ID_H263, MKTAG('h', '2', '6', '3') }, /* H263 */ + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', ' ') }, /* DV NTSC */ + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', 'p') }, /* DV PAL */ + { 0, 0 }, +}; + +static const CodecTag mov_audio_tags[] = { +/* { CODEC_ID_PCM_S16BE, MKTAG('N', 'O', 'N', 'E') }, *//* uncompressed */ + { CODEC_ID_PCM_S16BE, MKTAG('t', 'w', 'o', 's') }, /* 16 bits */ + { CODEC_ID_PCM_S8, MKTAG('t', 'w', 'o', 's') }, /* 8 bits */ + { CODEC_ID_PCM_U8, 0x20776172 }, /* 8 bits unsigned */ + { CODEC_ID_PCM_S16LE, MKTAG('s', 'o', 'w', 't') }, /* */ + { CODEC_ID_PCM_MULAW, MKTAG('u', 'l', 'a', 'w') }, /* */ + { CODEC_ID_PCM_ALAW, MKTAG('a', 'l', 'a', 'w') }, /* */ + { CODEC_ID_ADPCM_IMA_QT, MKTAG('i', 'm', 'a', '4') }, /* IMA-4 ADPCM */ + { CODEC_ID_MACE3, MKTAG('M', 'A', 'C', '3') }, /* Macintosh Audio Compression and Expansion 3:1 */ + { CODEC_ID_MACE6, MKTAG('M', 'A', 'C', '6') }, /* Macintosh Audio Compression and Expansion 6:1 */ + + { CODEC_ID_MP2, MKTAG('.', 'm', 'p', '3') }, /* MPEG layer 3 */ /* sample files at http://www.3ivx.com/showcase.html use this tag */ + { CODEC_ID_MP2, 0x6D730055 }, /* MPEG layer 3 */ + { CODEC_ID_MP2, 0x5500736D }, /* MPEG layer 3 *//* XXX: check endianness */ +/* { CODEC_ID_OGG_VORBIS, MKTAG('O', 'g', 'g', 'S') }, *//* sample files at http://heroinewarrior.com/xmovie.php3 use this tag */ +/* MP4 tags */ +/* { CODEC_ID_AAC, MKTAG('m', 'p', '4', 'a') }, *//* MPEG 4 AAC or audio ? */ + /* The standard for mpeg4 audio is still not normalised AFAIK anyway */ + { 0, 0 }, +}; + +/* the QuickTime file format is quite convoluted... + * it has lots of index tables, each indexing something in another one... + * Here we just use what is needed to read the chunks + */ + +typedef struct MOV_sample_to_chunk_tbl { + long first; + long count; + long id; +} MOV_sample_to_chunk_tbl; + +typedef struct MOVStreamContext { + int ffindex; /* the ffmpeg stream id */ + int is_ff_stream; /* Is this stream presented to ffmpeg ? i.e. is this an audio or video stream ? */ + long next_chunk; + long chunk_count; + INT64 *chunk_offsets; + long sample_to_chunk_sz; + MOV_sample_to_chunk_tbl *sample_to_chunk; + long sample_to_chunk_index; + long sample_size; + long sample_count; + long *sample_sizes; + long time_scale; + long current_sample; + long left_in_chunk; /* how many samples before next chunk */ + /* specific MPEG4 header which is added at the beginning of the stream */ + int header_len; + uint8_t *header_data; +} MOVStreamContext; + +typedef struct MOVContext { + int mp4; /* set to 1 as soon as we are sure that the file is an .mp4 file (even some header parsing depends on this) */ + AVFormatContext *fc; + long time_scale; + int found_moov; /* when both 'moov' and 'mdat' sections has been found */ + int found_mdat; /* we suppose we have enough data to read the file */ + INT64 mdat_size; + INT64 mdat_offset; + int total_streams; + /* some streams listed here aren't presented to the ffmpeg API, since they aren't either video nor audio + * but we need the info to be able to skip data from those streams in the 'mdat' section + */ + MOVStreamContext *streams[MAX_STREAMS]; + + INT64 next_chunk_offset; + int partial; /* != 0 : there is still to read in the current chunk (=id of the stream + 1) */ +} MOVContext; + + +struct MOVParseTableEntry; + +/* XXX: it's the first time I make a recursive parser I think... sorry if it's ugly :P */ + +/* those functions parse an atom */ +/* return code: + 1: found what I wanted, exit + 0: continue to parse next atom + -1: error occured, exit + */ +typedef int (*mov_parse_function)(const struct MOVParseTableEntry *parse_table, + ByteIOContext *pb, + UINT32 atom_type, + INT64 atom_offset, /* after the size and type field (and eventually the extended size) */ + INT64 atom_size, /* total size (excluding the size and type fields) */ + void *param); + +/* links atom IDs to parse functions */ +typedef struct MOVParseTableEntry { + UINT32 type; + mov_parse_function func; +} MOVParseTableEntry; + +static int parse_leaf(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ +#ifdef DEBUG + print_atom("leaf", atom_type, atom_offset, atom_size); +#endif + if(atom_size>1) + url_fskip(pb, atom_size); +/* url_seek(pb, atom_offset+atom_size, SEEK_SET); */ + return 0; +} + + +static int parse_default(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + UINT32 type, foo=0; + UINT64 offset, size; + UINT64 total_size = 0; + int i; + int err = 0; + foo=0; +#ifdef DEBUG + print_atom("default", atom_type, atom_offset, atom_size); + debug_indent++; +#endif + + offset = atom_offset; + + if(atom_size < 0) + atom_size = 0x0FFFFFFFFFFFFFFF; + while((total_size < atom_size) && !url_feof(pb) && !err) { + size=atom_size; + type=0L; + if(atom_size >= 8) { + size = get_be32(pb); + type = get_le32(pb); + } + total_size += 8; + offset+=8; +// printf("type: %08lx sz: %08lx", type, size); + if(size == 1) { /* 64 bit extended size */ + size = get_be64(pb); + offset+=8; + total_size+=8; + size-=8; + } + if(size == 0) + size = atom_size - total_size; + size-=8; + for(i=0; parse_table[i].type != 0L && parse_table[i].type != type; i++); + +// printf(" i=%ld\n", i); + if (parse_table[i].type == 0) { /* skip leaf atoms data */ +// url_seek(pb, atom_offset+atom_size, SEEK_SET); +#ifdef DEBUG + print_atom("unknown", type, offset, size); +#endif + url_fskip(pb, size); + } else + err = (parse_table[i].func)(parse_table, pb, type, offset, size, param); + + offset+=size; + total_size+=size; + } + +#ifdef DEBUG + debug_indent--; +#endif + return err; +} + +static int parse_mvhd(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; +#ifdef DEBUG + print_atom("mvhd", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + get_be32(pb); /* creation time */ + get_be32(pb); /* modification time */ + c->time_scale = get_be32(pb); /* time scale */ +#ifdef DEBUG + printf("time scale = %li\n", c->time_scale); +#endif + get_be32(pb); /* duration */ + get_be32(pb); /* preferred scale */ + + get_be16(pb); /* preferred volume */ + + url_fskip(pb, 10); /* reserved */ + + url_fskip(pb, 36); /* display matrix */ + + get_be32(pb); /* preview time */ + get_be32(pb); /* preview duration */ + get_be32(pb); /* poster time */ + get_be32(pb); /* selection time */ + get_be32(pb); /* selection duration */ + get_be32(pb); /* current time */ + get_be32(pb); /* next track ID */ + + return 0; +} + +/* this atom should contain all header atoms */ +static int parse_moov(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + int err; + MOVContext *c; +#ifdef DEBUG + print_atom("moov", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + + err = parse_default(parse_table, pb, atom_type, atom_offset, atom_size, param); + /* we parsed the 'moov' atom, we can terminate the parsing as soon as we find the 'mdat' */ + /* so we don't parse the whole file if over a network */ + c->found_moov=1; + if(c->found_mdat) + return 1; /* found both, just go */ + return 0; /* now go for mdat */ +} + +/* this atom contains actual media data */ +static int parse_mdat(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; +#ifdef DEBUG + print_atom("mdat", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + + if(atom_size == 0) /* wrong one (MP4) */ + return 0; + c->found_mdat=1; + c->mdat_offset = atom_offset; + c->mdat_size = atom_size; + if(c->found_moov) + return 1; /* found both, just go */ + url_fskip(pb, atom_size); + return 0; /* now go for moov */ +} + +/* this atom should be null (from specs), but some buggy files put the 'moov' atom inside it... */ +/* like the files created with Adobe Premiere 5.0, for samples see */ +/* http://graphics.tudelft.nl/~wouter/publications/soundtests/ */ +static int parse_wide(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + int err; + UINT32 type; +#ifdef DEBUG + print_atom("wide", atom_type, atom_offset, atom_size); + debug_indent++; +#endif + if (atom_size < 8) + return 0; /* continue */ + if (get_be32(pb) != 0) { /* 0 sized mdat atom... use the 'wide' atom size */ + url_fskip(pb, atom_size - 4); + return 0; + } + type = get_le32(pb); + if (type != MKTAG('m', 'd', 'a', 't')) { + url_fskip(pb, atom_size - 8); + return 0; + } + err = parse_mdat(parse_table, pb, type, atom_offset + 8, atom_size - 8, param); +#ifdef DEBUG + debug_indent--; +#endif + return err; +} + +static int parse_trak(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("trak", atom_type, atom_offset, atom_size); +#endif + + c = (MOVContext *)param; + st = av_new_stream(c->fc, c->fc->nb_streams); + if (!st) return -2; + sc = av_malloc(sizeof(MOVStreamContext)); + sc->sample_to_chunk_index = -1; + st->priv_data = sc; + st->codec.codec_type = CODEC_TYPE_MOV_OTHER; + c->streams[c->fc->nb_streams-1] = sc; + return parse_default(parse_table, pb, atom_type, atom_offset, atom_size, param); +} + +static int parse_tkhd(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + AVStream *st; +#ifdef DEBUG + print_atom("tkhd", atom_type, atom_offset, atom_size); +#endif + + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + + get_byte(pb); /* version */ + + get_byte(pb); get_byte(pb); + get_byte(pb); /* flags */ + /* + MOV_TRACK_ENABLED 0x0001 + MOV_TRACK_IN_MOVIE 0x0002 + MOV_TRACK_IN_PREVIEW 0x0004 + MOV_TRACK_IN_POSTER 0x0008 + */ + + get_be32(pb); /* creation time */ + get_be32(pb); /* modification time */ + st->id = (int)get_be32(pb); /* track id (NOT 0 !)*/ + get_be32(pb); /* reserved */ + get_be32(pb); /* duration */ + get_be32(pb); /* reserved */ + get_be32(pb); /* reserved */ + + get_be16(pb); /* layer */ + get_be16(pb); /* alternate group */ + get_be16(pb); /* volume */ + get_be16(pb); /* reserved */ + + url_fskip(pb, 36); /* display matrix */ + + /* those are fixed-point */ + st->codec.width = get_be32(pb) >> 16; /* track width */ + st->codec.height = get_be32(pb) >> 16; /* track height */ + + return 0; +} + +static int parse_mdhd(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + AVStream *st; +#ifdef DEBUG + print_atom("mdhd", atom_type, atom_offset, atom_size); +#endif + + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + + get_byte(pb); /* version */ + + get_byte(pb); get_byte(pb); + get_byte(pb); /* flags */ + + get_be32(pb); /* creation time */ + get_be32(pb); /* modification time */ + + c->streams[c->total_streams]->time_scale = get_be32(pb); + +#ifdef DEBUG + printf("track[%i].time_scale = %li\n", c->fc->nb_streams-1, c->streams[c->total_streams]->time_scale); /* time scale */ +#endif + get_be32(pb); /* duration */ + + get_be16(pb); /* language */ + get_be16(pb); /* quality */ + + return 0; +} + +static int parse_hdlr(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int len = 0; + char *buf; + UINT32 type; + AVStream *st; + UINT32 ctype; +#ifdef DEBUG + print_atom("hdlr", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + /* component type */ + ctype = get_le32(pb); + type = get_le32(pb); /* component subtype */ + +#ifdef DEBUG + printf("ctype= %c%c%c%c (0x%08lx)\n", *((char *)&ctype), ((char *)&ctype)[1], ((char *)&ctype)[2], ((char *)&ctype)[3], (long) ctype); + printf("stype= %c%c%c%c\n", *((char *)&type), ((char *)&type)[1], ((char *)&type)[2], ((char *)&type)[3]); +#endif +#ifdef DEBUG +/* XXX: yeah this is ugly... */ + if(ctype == MKTAG('m', 'h', 'l', 'r')) { /* MOV */ + if(type == MKTAG('v', 'i', 'd', 'e')) + puts("hdlr: vide"); + else if(type == MKTAG('s', 'o', 'u', 'n')) + puts("hdlr: soun"); + } else if(ctype == 0) { /* MP4 */ + if(type == MKTAG('v', 'i', 'd', 'e')) + puts("hdlr: vide"); + else if(type == MKTAG('s', 'o', 'u', 'n')) + puts("hdlr: soun"); + else if(type == MKTAG('o', 'd', 's', 'm')) + puts("hdlr: odsm"); + else if(type == MKTAG('s', 'd', 's', 'm')) + puts("hdlr: sdsm"); + } else puts("hdlr: meta"); +#endif + + if(ctype == MKTAG('m', 'h', 'l', 'r')) { /* MOV */ + /* helps parsing the string hereafter... */ + c->mp4 = 0; + if(type == MKTAG('v', 'i', 'd', 'e')) + st->codec.codec_type = CODEC_TYPE_VIDEO; + else if(type == MKTAG('s', 'o', 'u', 'n')) + st->codec.codec_type = CODEC_TYPE_AUDIO; + } else if(ctype == 0) { /* MP4 */ + /* helps parsing the string hereafter... */ + c->mp4 = 1; + if(type == MKTAG('v', 'i', 'd', 'e')) + st->codec.codec_type = CODEC_TYPE_VIDEO; + else if(type == MKTAG('s', 'o', 'u', 'n')) + st->codec.codec_type = CODEC_TYPE_AUDIO; + } + get_be32(pb); /* component manufacture */ + get_be32(pb); /* component flags */ + get_be32(pb); /* component flags mask */ + + if(atom_size <= 24) + return 0; /* nothing left to read */ + /* XXX: MP4 uses a C string, not a pascal one */ + /* component name */ + + if(c->mp4) { + /* .mp4: C string */ + while(get_byte(pb) && (++len < (atom_size - 24))); + } else { + /* .mov: PASCAL string */ + len = get_byte(pb); + buf = av_malloc(len+1); + get_buffer(pb, buf, len); + buf[len] = '\0'; +#ifdef DEBUG + printf("**buf='%s'\n", buf); +#endif + av_free(buf); + } +#if 0 + len = get_byte(pb); + /* XXX: use a better heuristic */ + if(len < 32) { + /* assume that it is a Pascal like string */ + buf = av_malloc(len+1); + get_buffer(pb, buf, len); + buf[len] = '\0'; +#ifdef DEBUG + printf("**buf='%s'\n", buf); +#endif + av_free(buf); + } else { + /* MP4 string */ + for(;;) { + if (len == 0) + break; + len = get_byte(pb); + } + } +#endif + + return 0; +} + +static int mp4_read_descr_len(ByteIOContext *pb) +{ + int c, len, count; + + len = 0; + count = 0; + for(;;) { + c = get_byte(pb); + len = (len << 7) | (c & 0x7f); + if ((c & 0x80) == 0) + break; + if (++count == 4) + break; + } + return len; +} + +static int mp4_read_descr(ByteIOContext *pb, int *tag) +{ + int len; + *tag = get_byte(pb); + len = mp4_read_descr_len(pb); +#ifdef DEBUG + printf("MPEG4 description: tag=0x%02x len=%d\n", *tag, len); +#endif + return len; +} + +static int parse_stsd(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int entries, size, samp_sz, frames_per_sample, id; + UINT32 format; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("stsd", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = (MOVStreamContext *)st->priv_data; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + entries = get_be32(pb); + + while(entries--) { + size = get_be32(pb); /* size */ + format = get_le32(pb); /* data format */ + + get_be32(pb); /* reserved */ + get_be16(pb); /* reserved */ + get_be16(pb); /* index */ + + /* for MPEG4: set codec type by looking for it */ + id = codec_get_id(mov_video_tags, format); + if (id >= 0) { + AVCodec *codec; + codec = avcodec_find_decoder(id); + if (codec) + st->codec.codec_type = codec->type; + } +#ifdef DEBUG + printf("size=%d 4CC= %c%c%c%c codec_type=%d\n", + size, + (format >> 0) & 0xff, + (format >> 8) & 0xff, + (format >> 16) & 0xff, + (format >> 24) & 0xff, + st->codec.codec_type); +#endif + if(st->codec.codec_type==CODEC_TYPE_VIDEO) { + st->codec.codec_tag = format; + st->codec.codec_id = codec_get_id(mov_video_tags, format); + get_be16(pb); /* version */ + get_be16(pb); /* revision level */ + get_be32(pb); /* vendor */ + get_be32(pb); /* temporal quality */ + get_be32(pb); /* spacial quality */ + st->codec.width = get_be16(pb); /* width */ + st->codec.height = get_be16(pb); /* height */ +#if 1 + if (st->codec.codec_id == CODEC_ID_MPEG4) { + /* in some MPEG4 the width/height are not correct, so + we ignore this info */ + st->codec.width = 0; + st->codec.height = 0; + } +#endif + get_be32(pb); /* horiz resolution */ + get_be32(pb); /* vert resolution */ + get_be32(pb); /* data size, always 0 */ + frames_per_sample = get_be16(pb); /* frame per samples */ +#ifdef DEBUG + printf("frames/samples = %d\n", frames_per_sample); +#endif + url_fskip(pb, 32); /* codec name */ + + get_be16(pb); /* depth */ + get_be16(pb); /* colortable id */ + + st->codec.frame_rate = 25 * FRAME_RATE_BASE; + + size -= (16+8*4+2+32+2*2); + while (size >= 8) { + int atom_size, atom_type; + INT64 start_pos; + + atom_size = get_be32(pb); + atom_type = get_le32(pb); + size -= 8; +#ifdef DEBUG + printf("VIDEO: atom_type=%c%c%c%c atom_size=%d size_left=%d\n", + (atom_type >> 0) & 0xff, + (atom_type >> 8) & 0xff, + (atom_type >> 16) & 0xff, + (atom_type >> 24) & 0xff, + atom_size, size); +#endif + start_pos = url_ftell(pb); + + switch(atom_type) { + case MKTAG('e', 's', 'd', 's'): + { + int tag, len; + /* Well, broken but suffisant for some MP4 streams */ + get_be32(pb); /* version + flags */ + len = mp4_read_descr(pb, &tag); + if (tag == 0x03) { + /* MP4ESDescrTag */ + get_be16(pb); /* ID */ + get_byte(pb); /* priority */ + len = mp4_read_descr(pb, &tag); + if (tag != 0x04) + goto fail; + /* MP4DecConfigDescrTag */ + get_byte(pb); /* objectTypeId */ + get_be32(pb); /* streamType + buffer size */ + get_be32(pb); /* max bit rate */ + get_be32(pb); /* avg bit rate */ + len = mp4_read_descr(pb, &tag); + if (tag != 0x05) + goto fail; + /* MP4DecSpecificDescrTag */ +#ifdef DEBUG + printf("Specific MPEG4 header len=%d\n", len); +#endif + sc->header_data = av_mallocz(len); + if (sc->header_data) { + get_buffer(pb, sc->header_data, len); + sc->header_len = len; + } + } + /* in any case, skip garbage */ + } + break; + default: + break; + } + fail: + url_fskip(pb, (atom_size - 8) - + ((url_ftell(pb) - start_pos))); + size -= atom_size - 8; + } + if (size > 0) { + /* unknown extension */ + url_fskip(pb, size); + } + } else { + st->codec.codec_tag = format; + + get_be16(pb); /* version */ + get_be16(pb); /* revision level */ + get_be32(pb); /* vendor */ + + st->codec.channels = get_be16(pb);/* channel count */ + samp_sz = get_be16(pb); /* sample size */ +#ifdef DEBUG + if(samp_sz != 16) + puts("!!! stsd: audio sample size is not 16 bit !"); +#endif + st->codec.codec_id = codec_get_id(mov_audio_tags, format); + /* handle specific s8 codec */ + if (st->codec.codec_id == CODEC_ID_PCM_S16BE && samp_sz == 8) + st->codec.codec_id = CODEC_ID_PCM_S8; + + get_be16(pb); /* compression id = 0*/ + get_be16(pb); /* packet size = 0 */ + + st->codec.sample_rate = ((get_be32(pb) >> 16)); + st->codec.bit_rate = 0; +#if 0 + + get_be16(pb); get_be16(pb); /* */ + get_be16(pb); /* */ + get_be16(pb); /* */ + get_be16(pb); /* */ + get_be16(pb); /* */ +#endif + if(size > 16) + url_fskip(pb, size-(16+20)); + } + } +/* + if(len) { + buf = av_malloc(len+1); + get_buffer(pb, buf, len); + buf[len] = '\0'; + puts(buf); + av_free(buf); + } +*/ + return 0; +} + +static int parse_stco(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int entries, i; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("stco", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = (MOVStreamContext *)st->priv_data; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + entries = get_be32(pb); + sc->chunk_count = entries; + sc->chunk_offsets = av_malloc(entries * sizeof(INT64)); + if(atom_type == MKTAG('s', 't', 'c', 'o')) { + for(i=0; i<entries; i++) { + sc->chunk_offsets[i] = get_be32(pb); + } + } else if(atom_type == MKTAG('c', 'o', '6', '4')) { + for(i=0; i<entries; i++) { + sc->chunk_offsets[i] = get_be64(pb); + } + } else + return -1; +#ifdef DEBUG +/* + for(i=0; i<entries; i++) { + printf("chunk offset=0x%Lx\n", sc->chunk_offsets[i]); + } +*/ +#endif + return 0; +} + +static int parse_stsc(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int entries, i; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("stsc", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = (MOVStreamContext *)st->priv_data; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + entries = get_be32(pb); +#ifdef DEBUG +printf("track[%i].stsc.entries = %i\n", c->fc->nb_streams-1, entries); +#endif + sc->sample_to_chunk_sz = entries; + sc->sample_to_chunk = av_malloc(entries * sizeof(MOV_sample_to_chunk_tbl)); + for(i=0; i<entries; i++) { + sc->sample_to_chunk[i].first = get_be32(pb); + sc->sample_to_chunk[i].count = get_be32(pb); + sc->sample_to_chunk[i].id = get_be32(pb); +#ifdef DEBUG +/* printf("sample_to_chunk first=%ld count=%ld, id=%ld\n", sc->sample_to_chunk[i].first, sc->sample_to_chunk[i].count, sc->sample_to_chunk[i].id); */ +#endif + } + return 0; +} + +static int parse_stsz(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int entries, i; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("stsz", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = (MOVStreamContext *)st->priv_data; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + + sc->sample_size = get_be32(pb); + entries = get_be32(pb); + sc->sample_count = entries; +#ifdef DEBUG + printf("sample_size = %ld sample_count = %ld\n", sc->sample_size, sc->sample_count); +#endif + if(sc->sample_size) + return 0; /* there isn't any table following */ + sc->sample_sizes = av_malloc(entries * sizeof(long)); + for(i=0; i<entries; i++) { + sc->sample_sizes[i] = get_be32(pb); +#ifdef DEBUG +/* printf("sample_sizes[]=%ld\n", sc->sample_sizes[i]); */ +#endif + } + return 0; +} + +static int parse_stts(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + int entries, i; + AVStream *st; + MOVStreamContext *sc; +#ifdef DEBUG + print_atom("stts", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = (MOVStreamContext *)st->priv_data; + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + entries = get_be32(pb); +#ifdef DEBUG +printf("track[%i].stts.entries = %i\n", c->fc->nb_streams-1, entries); +#endif + for(i=0; i<entries; i++) { + int sample_duration; + + get_be32(pb); + sample_duration = get_be32(pb); + + if (!i && st->codec.codec_type==CODEC_TYPE_VIDEO) { + st->codec.frame_rate = FRAME_RATE_BASE * c->streams[c->total_streams]->time_scale; + if (sample_duration) + st->codec.frame_rate /= sample_duration; +#ifdef DEBUG + printf("VIDEO FRAME RATE= %i (sd= %i)\n", st->codec.frame_rate, sample_duration); +#endif + } + } + return 0; +} + +#ifdef CONFIG_ZLIB +static int null_read_packet(void *opaque, UINT8 *buf, int buf_size) +{ + return -1; +} + +static int parse_cmov(const MOVParseTableEntry *parse_table, ByteIOContext *pb, UINT32 atom_type, INT64 atom_offset, INT64 atom_size, void *param) +{ + MOVContext *c; + ByteIOContext ctx; + char *cmov_data; + unsigned char *moov_data; /* uncompressed data */ + long cmov_len, moov_len; + int ret; +#ifdef DEBUG + print_atom("cmov", atom_type, atom_offset, atom_size); +#endif + c = (MOVContext *)param; + + get_be32(pb); /* dcom atom */ + if (get_le32(pb) != MKTAG( 'd', 'c', 'o', 'm' )) + return -1; + if (get_le32(pb) != MKTAG( 'z', 'l', 'i', 'b' )) { + puts("unknown compression for cmov atom !"); + return -1; + } + get_be32(pb); /* cmvd atom */ + if (get_le32(pb) != MKTAG( 'c', 'm', 'v', 'd' )) + return -1; + moov_len = get_be32(pb); /* uncompressed size */ + cmov_len = atom_size - 6 * 4; + + cmov_data = av_malloc(cmov_len); + if (!cmov_data) + return -1; + moov_data = av_malloc(moov_len); + if (!moov_data) { + av_free(cmov_data); + return -1; + } + get_buffer(pb, cmov_data, cmov_len); + if(uncompress (moov_data, &moov_len, (const Bytef *)cmov_data, cmov_len) != Z_OK) + return -1; + if(init_put_byte(&ctx, moov_data, moov_len, 0, NULL, null_read_packet, NULL, NULL) != 0) + return -1; + ctx.buf_end = ctx.buffer + moov_len; + ret = parse_default(parse_table, &ctx, MKTAG( 'm', 'o', 'o', 'v' ), 0, moov_len, param); + av_free(moov_data); + av_free(cmov_data); + return ret; +} +#endif + +static const MOVParseTableEntry mov_default_parse_table[] = { +/* mp4 atoms */ +{ MKTAG( 'm', 'p', '4', 'a' ), parse_default }, +{ MKTAG( 'c', 'o', '6', '4' ), parse_stco }, +{ MKTAG( 's', 't', 'c', 'o' ), parse_stco }, +{ MKTAG( 'c', 'r', 'h', 'd' ), parse_default }, +{ MKTAG( 'c', 't', 't', 's' ), parse_leaf }, +{ MKTAG( 'c', 'p', 'r', 't' ), parse_default }, +{ MKTAG( 'u', 'r', 'l', ' ' ), parse_leaf }, +{ MKTAG( 'u', 'r', 'n', ' ' ), parse_leaf }, +{ MKTAG( 'd', 'i', 'n', 'f' ), parse_default }, +{ MKTAG( 'd', 'r', 'e', 'f' ), parse_leaf }, +{ MKTAG( 's', 't', 'd', 'p' ), parse_default }, +{ MKTAG( 'e', 's', 'd', 's' ), parse_default }, +{ MKTAG( 'e', 'd', 't', 's' ), parse_default }, +{ MKTAG( 'e', 'l', 's', 't' ), parse_leaf }, +{ MKTAG( 'u', 'u', 'i', 'd' ), parse_default }, +{ MKTAG( 'f', 'r', 'e', 'e' ), parse_leaf }, +{ MKTAG( 'h', 'd', 'l', 'r' ), parse_hdlr }, +{ MKTAG( 'h', 'm', 'h', 'd' ), parse_leaf }, +{ MKTAG( 'h', 'i', 'n', 't' ), parse_leaf }, +{ MKTAG( 'n', 'm', 'h', 'd' ), parse_leaf }, +{ MKTAG( 'm', 'p', '4', 's' ), parse_default }, +{ MKTAG( 'm', 'd', 'i', 'a' ), parse_default }, +{ MKTAG( 'm', 'd', 'a', 't' ), parse_mdat }, +{ MKTAG( 'm', 'd', 'h', 'd' ), parse_mdhd }, +{ MKTAG( 'm', 'i', 'n', 'f' ), parse_default }, +{ MKTAG( 'm', 'o', 'o', 'v' ), parse_moov }, +{ MKTAG( 'm', 'v', 'h', 'd' ), parse_mvhd }, +{ MKTAG( 'i', 'o', 'd', 's' ), parse_leaf }, +{ MKTAG( 'o', 'd', 'h', 'd' ), parse_default }, +{ MKTAG( 'm', 'p', 'o', 'd' ), parse_leaf }, +{ MKTAG( 's', 't', 's', 'd' ), parse_stsd }, +{ MKTAG( 's', 't', 's', 'z' ), parse_stsz }, +{ MKTAG( 's', 't', 'b', 'l' ), parse_default }, +{ MKTAG( 's', 't', 's', 'c' ), parse_stsc }, +{ MKTAG( 's', 'd', 'h', 'd' ), parse_default }, +{ MKTAG( 's', 't', 's', 'h' ), parse_default }, +{ MKTAG( 's', 'k', 'i', 'p' ), parse_default }, +{ MKTAG( 's', 'm', 'h', 'd' ), parse_leaf }, +{ MKTAG( 'd', 'p', 'n', 'd' ), parse_leaf }, +{ MKTAG( 's', 't', 's', 's' ), parse_leaf }, +{ MKTAG( 's', 't', 't', 's' ), parse_stts }, +{ MKTAG( 't', 'r', 'a', 'k' ), parse_trak }, +{ MKTAG( 't', 'k', 'h', 'd' ), parse_tkhd }, +{ MKTAG( 't', 'r', 'e', 'f' ), parse_default }, /* not really */ +{ MKTAG( 'u', 'd', 't', 'a' ), parse_leaf }, +{ MKTAG( 'v', 'm', 'h', 'd' ), parse_leaf }, +{ MKTAG( 'm', 'p', '4', 'v' ), parse_default }, +/* extra mp4 */ +{ MKTAG( 'M', 'D', 'E', 'S' ), parse_leaf }, +/* QT atoms */ +{ MKTAG( 'c', 'h', 'a', 'p' ), parse_leaf }, +{ MKTAG( 'c', 'l', 'i', 'p' ), parse_default }, +{ MKTAG( 'c', 'r', 'g', 'n' ), parse_leaf }, +{ MKTAG( 'k', 'm', 'a', 't' ), parse_leaf }, +{ MKTAG( 'm', 'a', 't', 't' ), parse_default }, +{ MKTAG( 'r', 'd', 'r', 'f' ), parse_leaf }, +{ MKTAG( 'r', 'm', 'd', 'a' ), parse_default }, +{ MKTAG( 'r', 'm', 'd', 'r' ), parse_leaf }, +//{ MKTAG( 'r', 'm', 'q', 'u' ), parse_leaf }, +{ MKTAG( 'r', 'm', 'r', 'a' ), parse_default }, +{ MKTAG( 's', 'c', 'p', 't' ), parse_leaf }, +{ MKTAG( 's', 'y', 'n', 'c' ), parse_leaf }, +{ MKTAG( 's', 's', 'r', 'c' ), parse_leaf }, +{ MKTAG( 't', 'c', 'm', 'd' ), parse_leaf }, +{ MKTAG( 'w', 'i', 'd', 'e' ), parse_wide }, /* place holder */ +#ifdef CONFIG_ZLIB +{ MKTAG( 'c', 'm', 'o', 'v' ), parse_cmov }, +#else +{ MKTAG( 'c', 'm', 'o', 'v' ), parse_leaf }, +#endif +{ 0L, parse_leaf } +}; + +static void mov_free_stream_context(MOVStreamContext *sc) +{ + if(sc) { + av_free(sc->chunk_offsets); + av_free(sc->sample_to_chunk); + av_free(sc->header_data); + av_free(sc); + } +} + +static uint32_t to_tag(uint8_t *buf) +{ + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); +} + +static uint32_t to_be32(uint8_t *buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + +/* XXX: is it suffisant ? */ +static int mov_probe(AVProbeData *p) +{ + unsigned int offset; + uint32_t tag; + + /* check file header */ + if (p->buf_size <= 12) + return 0; + offset = 0; + for(;;) { + /* ignore invalid offset */ + if ((offset + 8) > (unsigned int)p->buf_size) + return 0; + tag = to_tag(p->buf + offset + 4); + switch(tag) { + case MKTAG( 'm', 'o', 'o', 'v' ): + case MKTAG( 'w', 'i', 'd', 'e' ): + case MKTAG( 'f', 'r', 'e', 'e' ): + case MKTAG( 'm', 'd', 'a', 't'): + return AVPROBE_SCORE_MAX; + case MKTAG( 'f', 't', 'y', 'p' ): + case MKTAG( 's', 'k', 'i', 'p' ): + offset = to_be32(p->buf) + offset; + break; + default: + /* unrecognized tag */ + return 0; + } + } + return 0; +} + +static int mov_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + MOVContext *mov = s->priv_data; + ByteIOContext *pb = &s->pb; + int i, j, nb, err; + INT64 size; + + mov->fc = s; +#if 0 + /* XXX: I think we should auto detect */ + if(s->iformat->name[1] == 'p') + mov->mp4 = 1; +#endif + if(!url_is_streamed(pb)) /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */ + size = url_filesize(url_fileno(pb)); + else + size = 0x7FFFFFFFFFFFFFFF; + +#ifdef DEBUG + printf("filesz=%Ld\n", size); +#endif + + /* check MOV header */ + err = parse_default(mov_default_parse_table, pb, 0L, 0LL, size, mov); + if(err<0 || (!mov->found_moov || !mov->found_mdat)) { + puts("header not found !!!"); + exit(1); + } +#ifdef DEBUG + printf("on_parse_exit_offset=%d\n", (int) url_ftell(pb)); +#endif + /* some cleanup : make sure we are on the mdat atom */ + if(!url_is_streamed(pb) && (url_ftell(pb) != mov->mdat_offset)) + url_fseek(pb, mov->mdat_offset, SEEK_SET); + + mov->next_chunk_offset = mov->mdat_offset; /* initialise reading */ + +#ifdef DEBUG + printf("mdat_reset_offset=%d\n", (int) url_ftell(pb)); +#endif + +#ifdef DEBUG + printf("streams= %d\n", s->nb_streams); +#endif + mov->total_streams = nb = s->nb_streams; + +#if 1 + for(i=0; i<s->nb_streams;) { + if(s->streams[i]->codec.codec_type == CODEC_TYPE_MOV_OTHER) {/* not audio, not video, delete */ + av_free(s->streams[i]); + for(j=i+1; j<s->nb_streams; j++) + s->streams[j-1] = s->streams[j]; + s->nb_streams--; + } else + i++; + } + for(i=0; i<s->nb_streams;i++) { + MOVStreamContext *sc; + sc = (MOVStreamContext *)s->streams[i]->priv_data; + sc->ffindex = i; + sc->is_ff_stream = 1; + } +#endif +#ifdef DEBUG + printf("real streams= %d\n", s->nb_streams); +#endif + return 0; +} + +/* Yes, this is ugly... I didn't write the specs of QT :p */ +/* XXX:remove useless commented code sometime */ +static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MOVContext *mov = s->priv_data; + MOVStreamContext *sc; + INT64 offset = 0x0FFFFFFFFFFFFFFF; + int i; + int st_id = 0, size; + size = 0x0FFFFFFF; + +#ifdef MOV_SPLIT_CHUNKS + if (mov->partial) { + + int idx; + + st_id = mov->partial - 1; + idx = mov->streams[st_id]->sample_to_chunk_index; + if (idx < 0) return 0; + size = mov->streams[st_id]->sample_sizes[mov->streams[st_id]->current_sample]; + + mov->streams[st_id]->current_sample++; + mov->streams[st_id]->left_in_chunk--; + + if(mov->streams[st_id]->left_in_chunk <= 0) + mov->partial = 0; + offset = mov->next_chunk_offset; + /* extract the sample */ + + goto readchunk; + } +#endif + +again: + for(i=0; i<mov->total_streams; i++) { + if((mov->streams[i]->next_chunk < mov->streams[i]->chunk_count) + && (mov->streams[i]->chunk_offsets[mov->streams[i]->next_chunk] < offset)) { + st_id = i; + offset = mov->streams[i]->chunk_offsets[mov->streams[i]->next_chunk]; + } + } + mov->streams[st_id]->next_chunk++; + if(offset==0x0FFFFFFFFFFFFFFF) + return -1; + + if(mov->next_chunk_offset < offset) { /* some meta data */ + url_fskip(&s->pb, (offset - mov->next_chunk_offset)); + mov->next_chunk_offset = offset; + } + +//printf("chunk: [%i] %lli -> %lli\n", st_id, mov->next_chunk_offset, offset); + if(!mov->streams[st_id]->is_ff_stream) { + url_fskip(&s->pb, (offset - mov->next_chunk_offset)); + mov->next_chunk_offset = offset; + offset = 0x0FFFFFFFFFFFFFFF; + goto again; + } + + /* now get the chunk size... */ + + for(i=0; i<mov->total_streams; i++) { + if((mov->streams[i]->next_chunk < mov->streams[i]->chunk_count) + && ((mov->streams[i]->chunk_offsets[mov->streams[i]->next_chunk] - offset) < size)) { + size = mov->streams[i]->chunk_offsets[mov->streams[i]->next_chunk] - offset; + } + } +#ifdef MOV_SPLIT_CHUNKS + /* split chunks into samples */ + if(mov->streams[st_id]->sample_size == 0) { + int idx; + idx = mov->streams[st_id]->sample_to_chunk_index; + if ((idx + 1 < mov->streams[st_id]->sample_to_chunk_sz) + && (mov->streams[st_id]->next_chunk >= mov->streams[st_id]->sample_to_chunk[idx + 1].first)) + idx++; + mov->streams[st_id]->sample_to_chunk_index = idx; + if(idx >= 0 && mov->streams[st_id]->sample_to_chunk[idx].count != 1) { + mov->partial = st_id+1; + /* we'll have to get those samples before next chunk */ + mov->streams[st_id]->left_in_chunk = (mov->streams[st_id]->sample_to_chunk[idx].count) - 1; + size = mov->streams[st_id]->sample_sizes[mov->streams[st_id]->current_sample]; + } + + mov->streams[st_id]->current_sample++; + } +#endif + +readchunk: +//printf("chunk: [%i] %lli -> %lli (%i)\n", st_id, offset, offset + size, size); + if(size == 0x0FFFFFFF) + size = mov->mdat_size + mov->mdat_offset - offset; + if(size < 0) + return -1; + if(size == 0) + return -1; + url_fseek(&s->pb, offset, SEEK_SET); + sc = mov->streams[st_id]; + if (sc->header_len > 0) { + av_new_packet(pkt, size + sc->header_len); + memcpy(pkt->data, sc->header_data, sc->header_len); + get_buffer(&s->pb, pkt->data + sc->header_len, size); + /* free header */ + av_freep(&sc->header_data); + sc->header_len = 0; + } else { + av_new_packet(pkt, size); + get_buffer(&s->pb, pkt->data, pkt->size); + } + pkt->stream_index = sc->ffindex; + +#ifdef DEBUG +/* + printf("Packet (%d, %d, %ld) ", pkt->stream_index, st_id, pkt->size); + for(i=0; i<8; i++) + printf("%02x ", pkt->data[i]); + for(i=0; i<8; i++) + printf("%c ", (pkt->data[i]) & 0x7F); + puts(""); +*/ +#endif + + mov->next_chunk_offset = offset + size; + + return 0; +} + +static int mov_read_close(AVFormatContext *s) +{ + int i; + MOVContext *mov = s->priv_data; + for(i=0; i<mov->total_streams; i++) + mov_free_stream_context(mov->streams[i]); + for(i=0; i<s->nb_streams; i++) + av_freep(&s->streams[i]); + return 0; +} + +static AVInputFormat mov_iformat = { + "mov", + "QuickTime/MPEG4 format", + sizeof(MOVContext), + mov_probe, + mov_read_header, + mov_read_packet, + mov_read_close, +}; + +int mov_init(void) +{ + av_register_input_format(&mov_iformat); + return 0; +} diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c new file mode 100644 index 0000000000..748841881d --- /dev/null +++ b/libavformat/mpeg.c @@ -0,0 +1,685 @@ +/* + * MPEG1/2 mux/demux + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#define MAX_PAYLOAD_SIZE 4096 +#define NB_STREAMS 2 + +typedef struct { + UINT8 buffer[MAX_PAYLOAD_SIZE]; + int buffer_ptr; + UINT8 id; + int max_buffer_size; /* in bytes */ + int packet_number; + INT64 start_pts; +} StreamInfo; + +typedef struct { + int packet_size; /* required packet size */ + int packet_data_max_size; /* maximum data size inside a packet */ + int packet_number; + int pack_header_freq; /* frequency (in packets^-1) at which we send pack headers */ + int system_header_freq; + int mux_rate; /* bitrate in units of 50 bytes/s */ + /* stream info */ + int audio_bound; + int video_bound; + int is_mpeg2; + int is_vcd; +} MpegMuxContext; + +#define PACK_START_CODE ((unsigned int)0x000001ba) +#define SYSTEM_HEADER_START_CODE ((unsigned int)0x000001bb) +#define SEQUENCE_END_CODE ((unsigned int)0x000001b7) +#define PACKET_START_CODE_MASK ((unsigned int)0xffffff00) +#define PACKET_START_CODE_PREFIX ((unsigned int)0x00000100) +#define ISO_11172_END_CODE ((unsigned int)0x000001b9) + +/* mpeg2 */ +#define PROGRAM_STREAM_MAP 0x1bc +#define PRIVATE_STREAM_1 0x1bd +#define PADDING_STREAM 0x1be +#define PRIVATE_STREAM_2 0x1bf + + +#define AUDIO_ID 0xc0 +#define VIDEO_ID 0xe0 + +extern AVOutputFormat mpeg1system_mux; +extern AVOutputFormat mpeg1vcd_mux; +extern AVOutputFormat mpeg2vob_mux; + +static int put_pack_header(AVFormatContext *ctx, + UINT8 *buf, INT64 timestamp) +{ + MpegMuxContext *s = ctx->priv_data; + PutBitContext pb; + + init_put_bits(&pb, buf, 128, NULL, NULL); + + put_bits(&pb, 32, PACK_START_CODE); + if (s->is_mpeg2) { + put_bits(&pb, 2, 0x2); + } else { + put_bits(&pb, 4, 0x2); + } + put_bits(&pb, 3, (UINT32)((timestamp >> 30) & 0x07)); + put_bits(&pb, 1, 1); + put_bits(&pb, 15, (UINT32)((timestamp >> 15) & 0x7fff)); + put_bits(&pb, 1, 1); + put_bits(&pb, 15, (UINT32)((timestamp) & 0x7fff)); + put_bits(&pb, 1, 1); + if (s->is_mpeg2) { + /* clock extension */ + put_bits(&pb, 9, 0); + put_bits(&pb, 1, 1); + } + put_bits(&pb, 1, 1); + put_bits(&pb, 22, s->mux_rate); + put_bits(&pb, 1, 1); + if (s->is_mpeg2) { + put_bits(&pb, 5, 0x1f); /* reserved */ + put_bits(&pb, 3, 0); /* stuffing length */ + } + flush_put_bits(&pb); + return pbBufPtr(&pb) - pb.buf; +} + +static int put_system_header(AVFormatContext *ctx, UINT8 *buf) +{ + MpegMuxContext *s = ctx->priv_data; + int size, rate_bound, i, private_stream_coded, id; + PutBitContext pb; + + init_put_bits(&pb, buf, 128, NULL, NULL); + + put_bits(&pb, 32, SYSTEM_HEADER_START_CODE); + put_bits(&pb, 16, 0); + put_bits(&pb, 1, 1); + + rate_bound = s->mux_rate; /* maximum bit rate of the multiplexed stream */ + put_bits(&pb, 22, rate_bound); + put_bits(&pb, 1, 1); /* marker */ + put_bits(&pb, 6, s->audio_bound); + + put_bits(&pb, 1, 1); /* variable bitrate */ + put_bits(&pb, 1, 1); /* non constrainted bit stream */ + + put_bits(&pb, 1, 0); /* audio locked */ + put_bits(&pb, 1, 0); /* video locked */ + put_bits(&pb, 1, 1); /* marker */ + + put_bits(&pb, 5, s->video_bound); + put_bits(&pb, 8, 0xff); /* reserved byte */ + + /* audio stream info */ + private_stream_coded = 0; + for(i=0;i<ctx->nb_streams;i++) { + StreamInfo *stream = ctx->streams[i]->priv_data; + id = stream->id; + if (id < 0xc0) { + /* special case for private streams (AC3 use that) */ + if (private_stream_coded) + continue; + private_stream_coded = 1; + id = 0xbd; + } + put_bits(&pb, 8, id); /* stream ID */ + put_bits(&pb, 2, 3); + if (id < 0xe0) { + /* audio */ + put_bits(&pb, 1, 0); + put_bits(&pb, 13, stream->max_buffer_size / 128); + } else { + /* video */ + put_bits(&pb, 1, 1); + put_bits(&pb, 13, stream->max_buffer_size / 1024); + } + } + flush_put_bits(&pb); + size = pbBufPtr(&pb) - pb.buf; + /* patch packet size */ + buf[4] = (size - 6) >> 8; + buf[5] = (size - 6) & 0xff; + + return size; +} + +static int mpeg_mux_init(AVFormatContext *ctx) +{ + MpegMuxContext *s = ctx->priv_data; + int bitrate, i, mpa_id, mpv_id, ac3_id; + AVStream *st; + StreamInfo *stream; + + s->packet_number = 0; + s->is_vcd = (ctx->oformat == &mpeg1vcd_mux); + s->is_mpeg2 = (ctx->oformat == &mpeg2vob_mux); + + if (s->is_vcd) + s->packet_size = 2324; /* VCD packet size */ + else + s->packet_size = 2048; + + /* startcode(4) + length(2) + flags(1) */ + s->packet_data_max_size = s->packet_size - 7; + s->audio_bound = 0; + s->video_bound = 0; + mpa_id = AUDIO_ID; + ac3_id = 0x80; + mpv_id = VIDEO_ID; + for(i=0;i<ctx->nb_streams;i++) { + st = ctx->streams[i]; + stream = av_mallocz(sizeof(StreamInfo)); + if (!stream) + goto fail; + st->priv_data = stream; + + switch(st->codec.codec_type) { + case CODEC_TYPE_AUDIO: + if (st->codec.codec_id == CODEC_ID_AC3) + stream->id = ac3_id++; + else + stream->id = mpa_id++; + stream->max_buffer_size = 4 * 1024; + s->audio_bound++; + break; + case CODEC_TYPE_VIDEO: + stream->id = mpv_id++; + stream->max_buffer_size = 46 * 1024; + s->video_bound++; + break; + default: + av_abort(); + } + } + + /* we increase slightly the bitrate to take into account the + headers. XXX: compute it exactly */ + bitrate = 2000; + for(i=0;i<ctx->nb_streams;i++) { + st = ctx->streams[i]; + bitrate += st->codec.bit_rate; + } + s->mux_rate = (bitrate + (8 * 50) - 1) / (8 * 50); + + if (s->is_vcd || s->is_mpeg2) + /* every packet */ + s->pack_header_freq = 1; + else + /* every 2 seconds */ + s->pack_header_freq = 2 * bitrate / s->packet_size / 8; + + if (s->is_mpeg2) + /* every 200 packets. Need to look at the spec. */ + s->system_header_freq = s->pack_header_freq * 40; + else if (s->is_vcd) + /* every 40 packets, this is my invention */ + s->system_header_freq = s->pack_header_freq * 40; + else + s->system_header_freq = s->pack_header_freq * 5; + + for(i=0;i<ctx->nb_streams;i++) { + stream = ctx->streams[i]->priv_data; + stream->buffer_ptr = 0; + stream->packet_number = 0; + stream->start_pts = -1; + } + return 0; + fail: + for(i=0;i<ctx->nb_streams;i++) { + av_free(ctx->streams[i]->priv_data); + } + return -ENOMEM; +} + +/* flush the packet on stream stream_index */ +static void flush_packet(AVFormatContext *ctx, int stream_index, int last_pkt) +{ + MpegMuxContext *s = ctx->priv_data; + StreamInfo *stream = ctx->streams[stream_index]->priv_data; + UINT8 *buf_ptr; + int size, payload_size, startcode, id, len, stuffing_size, i, header_len; + INT64 timestamp; + UINT8 buffer[128]; + int last = last_pkt ? 4 : 0; + + id = stream->id; + timestamp = stream->start_pts; + +#if 0 + printf("packet ID=%2x PTS=%0.3f\n", + id, timestamp / 90000.0); +#endif + + buf_ptr = buffer; + if (((s->packet_number % s->pack_header_freq) == 0)) { + /* output pack and systems header if needed */ + size = put_pack_header(ctx, buf_ptr, timestamp); + buf_ptr += size; + if ((s->packet_number % s->system_header_freq) == 0) { + size = put_system_header(ctx, buf_ptr); + buf_ptr += size; + } + } + size = buf_ptr - buffer; + put_buffer(&ctx->pb, buffer, size); + + /* packet header */ + if (s->is_mpeg2) { + header_len = 8; + } else { + header_len = 5; + } + payload_size = s->packet_size - (size + 6 + header_len + last); + if (id < 0xc0) { + startcode = PRIVATE_STREAM_1; + payload_size -= 4; + } else { + startcode = 0x100 + id; + } + stuffing_size = payload_size - stream->buffer_ptr; + if (stuffing_size < 0) + stuffing_size = 0; + + put_be32(&ctx->pb, startcode); + + put_be16(&ctx->pb, payload_size + header_len); + /* stuffing */ + for(i=0;i<stuffing_size;i++) + put_byte(&ctx->pb, 0xff); + + if (s->is_mpeg2) { + put_byte(&ctx->pb, 0x80); /* mpeg2 id */ + put_byte(&ctx->pb, 0x80); /* flags */ + put_byte(&ctx->pb, 0x05); /* header len (only pts is included) */ + } + put_byte(&ctx->pb, + (0x02 << 4) | + (((timestamp >> 30) & 0x07) << 1) | + 1); + put_be16(&ctx->pb, (UINT16)((((timestamp >> 15) & 0x7fff) << 1) | 1)); + put_be16(&ctx->pb, (UINT16)((((timestamp) & 0x7fff) << 1) | 1)); + + if (startcode == PRIVATE_STREAM_1) { + put_byte(&ctx->pb, id); + if (id >= 0x80 && id <= 0xbf) { + /* XXX: need to check AC3 spec */ + put_byte(&ctx->pb, 1); + put_byte(&ctx->pb, 0); + put_byte(&ctx->pb, 2); + } + } + + if (last_pkt) { + put_be32(&ctx->pb, ISO_11172_END_CODE); + } + /* output data */ + put_buffer(&ctx->pb, stream->buffer, payload_size - stuffing_size); + put_flush_packet(&ctx->pb); + + /* preserve remaining data */ + len = stream->buffer_ptr - payload_size; + if (len < 0) + len = 0; + memmove(stream->buffer, stream->buffer + stream->buffer_ptr - len, len); + stream->buffer_ptr = len; + + s->packet_number++; + stream->packet_number++; + stream->start_pts = -1; +} + +static int mpeg_mux_write_packet(AVFormatContext *ctx, int stream_index, + UINT8 *buf, int size, int pts) +{ + MpegMuxContext *s = ctx->priv_data; + AVStream *st = ctx->streams[stream_index]; + StreamInfo *stream = st->priv_data; + int len; + + while (size > 0) { + /* set pts */ + if (stream->start_pts == -1) { + stream->start_pts = pts; + } + len = s->packet_data_max_size - stream->buffer_ptr; + if (len > size) + len = size; + memcpy(stream->buffer + stream->buffer_ptr, buf, len); + stream->buffer_ptr += len; + buf += len; + size -= len; + while (stream->buffer_ptr >= s->packet_data_max_size) { + /* output the packet */ + if (stream->start_pts == -1) + stream->start_pts = pts; + flush_packet(ctx, stream_index, 0); + } + } + return 0; +} + +static int mpeg_mux_end(AVFormatContext *ctx) +{ + StreamInfo *stream; + int i; + + /* flush each packet */ + for(i=0;i<ctx->nb_streams;i++) { + stream = ctx->streams[i]->priv_data; + if (stream->buffer_ptr > 0) { + if (i == (ctx->nb_streams - 1)) + flush_packet(ctx, i, 1); + else + flush_packet(ctx, i, 0); + } + } + + /* write the end header */ + //put_be32(&ctx->pb, ISO_11172_END_CODE); + //put_flush_packet(&ctx->pb); + return 0; +} + +/*********************************************/ +/* demux code */ + +#define MAX_SYNC_SIZE 100000 + +static int mpegps_probe(AVProbeData *p) +{ + int code, c, i; + code = 0xff; + + /* we search the first start code. If it is a packet start code, + then we decide it is mpeg ps. We do not send highest value to + give a chance to mpegts */ + for(i=0;i<p->buf_size;i++) { + c = p->buf[i]; + code = (code << 8) | c; + if ((code & 0xffffff00) == 0x100) { + if (code == PACK_START_CODE || + code == SYSTEM_HEADER_START_CODE || + (code >= 0x1e0 && code <= 0x1ef) || + (code >= 0x1c0 && code <= 0x1df) || + code == PRIVATE_STREAM_2 || + code == PROGRAM_STREAM_MAP || + code == PRIVATE_STREAM_1 || + code == PADDING_STREAM) + return AVPROBE_SCORE_MAX - 1; + else + return 0; + } + } + return 0; +} + + +typedef struct MpegDemuxContext { + int header_state; +} MpegDemuxContext; + +static int find_start_code(ByteIOContext *pb, int *size_ptr, + UINT32 *header_state) +{ + unsigned int state, v; + int val, n; + + state = *header_state; + n = *size_ptr; + while (n > 0) { + if (url_feof(pb)) + break; + v = get_byte(pb); + n--; + if (state == 0x000001) { + state = ((state << 8) | v) & 0xffffff; + val = state; + goto found; + } + state = ((state << 8) | v) & 0xffffff; + } + val = -1; + found: + *header_state = state; + *size_ptr = n; + return val; +} + +static int mpegps_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + MpegDemuxContext *m = s->priv_data; + m->header_state = 0xff; + /* no need to do more */ + return 0; +} + +static INT64 get_pts(ByteIOContext *pb, int c) +{ + INT64 pts; + int val; + + if (c < 0) + c = get_byte(pb); + pts = (INT64)((c >> 1) & 0x07) << 30; + val = get_be16(pb); + pts |= (INT64)(val >> 1) << 15; + val = get_be16(pb); + pts |= (INT64)(val >> 1); + return pts; +} + +static int mpegps_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + MpegDemuxContext *m = s->priv_data; + AVStream *st; + int len, size, startcode, i, c, flags, header_len, type, codec_id; + INT64 pts, dts; + + /* next start code (should be immediately after) */ + redo: + m->header_state = 0xff; + size = MAX_SYNC_SIZE; + startcode = find_start_code(&s->pb, &size, &m->header_state); + //printf("startcode=%x pos=0x%Lx\n", startcode, url_ftell(&s->pb)); + if (startcode < 0) + return -EIO; + if (startcode == PACK_START_CODE) + goto redo; + if (startcode == SYSTEM_HEADER_START_CODE) + goto redo; + if (startcode == PADDING_STREAM || + startcode == PRIVATE_STREAM_2) { + /* skip them */ + len = get_be16(&s->pb); + url_fskip(&s->pb, len); + goto redo; + } + /* find matching stream */ + if (!((startcode >= 0x1c0 && startcode <= 0x1df) || + (startcode >= 0x1e0 && startcode <= 0x1ef) || + (startcode == 0x1bd))) + goto redo; + + len = get_be16(&s->pb); + pts = AV_NOPTS_VALUE; + dts = AV_NOPTS_VALUE; + /* stuffing */ + for(;;) { + c = get_byte(&s->pb); + len--; + /* XXX: for mpeg1, should test only bit 7 */ + if (c != 0xff) + break; + } + if ((c & 0xc0) == 0x40) { + /* buffer scale & size */ + get_byte(&s->pb); + c = get_byte(&s->pb); + len -= 2; + } + if ((c & 0xf0) == 0x20) { + pts = get_pts(&s->pb, c); + len -= 4; + } else if ((c & 0xf0) == 0x30) { + pts = get_pts(&s->pb, c); + dts = get_pts(&s->pb, -1); + len -= 9; + } else if ((c & 0xc0) == 0x80) { + /* mpeg 2 PES */ + if ((c & 0x30) != 0) { + fprintf(stderr, "Encrypted multiplex not handled\n"); + return -EIO; + } + flags = get_byte(&s->pb); + header_len = get_byte(&s->pb); + len -= 2; + if (header_len > len) + goto redo; + if ((flags & 0xc0) == 0x80) { + pts = get_pts(&s->pb, -1); + header_len -= 5; + len -= 5; + } if ((flags & 0xc0) == 0xc0) { + pts = get_pts(&s->pb, -1); + dts = get_pts(&s->pb, -1); + header_len -= 10; + len -= 10; + } + len -= header_len; + while (header_len > 0) { + get_byte(&s->pb); + header_len--; + } + } + if (startcode == 0x1bd) { + startcode = get_byte(&s->pb); + len--; + if (startcode >= 0x80 && startcode <= 0xbf) { + /* audio: skip header */ + get_byte(&s->pb); + get_byte(&s->pb); + get_byte(&s->pb); + len -= 3; + } + } + + /* now find stream */ + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + if (st->id == startcode) + goto found; + } + if (startcode >= 0x1e0 && startcode <= 0x1ef) { + type = CODEC_TYPE_VIDEO; + codec_id = CODEC_ID_MPEG1VIDEO; + } else if (startcode >= 0x1c0 && startcode <= 0x1df) { + type = CODEC_TYPE_AUDIO; + codec_id = CODEC_ID_MP2; + } else if (startcode >= 0x80 && startcode <= 0x9f) { + type = CODEC_TYPE_AUDIO; + codec_id = CODEC_ID_AC3; + } else { + skip: + /* skip packet */ + url_fskip(&s->pb, len); + goto redo; + } + /* no stream found: add a new stream */ + st = av_new_stream(s, startcode); + if (!st) + goto skip; + st->codec.codec_type = type; + st->codec.codec_id = codec_id; + found: + av_new_packet(pkt, len); + //printf("\nRead Packet ID: %x PTS: %f Size: %d", startcode, + // (float)pts/90000, len); + get_buffer(&s->pb, pkt->data, pkt->size); + pkt->pts = pts; + pkt->stream_index = st->index; + return 0; +} + +static int mpegps_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVOutputFormat mpeg1system_mux = { + "mpeg", + "MPEG1 System format", + "video/x-mpeg", + "mpg,mpeg", + sizeof(MpegMuxContext), + CODEC_ID_MP2, + CODEC_ID_MPEG1VIDEO, + mpeg_mux_init, + mpeg_mux_write_packet, + mpeg_mux_end, +}; + +static AVOutputFormat mpeg1vcd_mux = { + "vcd", + "MPEG1 System format (VCD)", + "video/x-mpeg", + NULL, + sizeof(MpegMuxContext), + CODEC_ID_MP2, + CODEC_ID_MPEG1VIDEO, + mpeg_mux_init, + mpeg_mux_write_packet, + mpeg_mux_end, +}; + +static AVOutputFormat mpeg2vob_mux = { + "vob", + "MPEG2 PS format (VOB)", + "video/x-mpeg", + "vob", + sizeof(MpegMuxContext), + CODEC_ID_MP2, + CODEC_ID_MPEG1VIDEO, + mpeg_mux_init, + mpeg_mux_write_packet, + mpeg_mux_end, +}; + +static AVInputFormat mpegps_demux = { + "mpeg", + "MPEG PS format", + sizeof(MpegDemuxContext), + mpegps_probe, + mpegps_read_header, + mpegps_read_packet, + mpegps_read_close, + .flags = AVFMT_NOHEADER, +}; + +int mpegps_init(void) +{ + av_register_output_format(&mpeg1system_mux); + av_register_output_format(&mpeg1vcd_mux); + av_register_output_format(&mpeg2vob_mux); + av_register_input_format(&mpegps_demux); + return 0; +} diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c new file mode 100644 index 0000000000..2947d960e8 --- /dev/null +++ b/libavformat/mpegts.c @@ -0,0 +1,316 @@ +/* + * MPEG2 transport stream (aka DVB) demux + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#define TS_FEC_PACKET_SIZE 204 +#define TS_PACKET_SIZE 188 +#define NB_PID_MAX 8192 + +enum MpegTSState { + MPEGTS_HEADER = 0, + MPEGTS_PESHEADER_FILL, + MPEGTS_PESHEADER_FLAGS, + MPEGTS_PESHEADER_SIZE, + MPEGTS_PESHEADER_READ, + MPEGTS_PAYLOAD, + MPEGTS_SKIP, +}; + +/* enough for PES header + length */ +#define MAX_HEADER_SIZE 6 + +typedef struct MpegTSStream { + int pid; + enum MpegTSState state; + int last_cc; /* last cc code (-1 if first packet) */ + /* used to get the format */ + int header_size; + int payload_size; + int pes_header_size; + AVStream *st; + unsigned char header[MAX_HEADER_SIZE]; +} MpegTSStream; + +typedef struct MpegTSContext { + int raw_packet_size; /* raw packet size, including FEC if present */ + MpegTSStream *pids[NB_PID_MAX]; +} MpegTSContext; + +/* autodetect fec presence. Must have at least 1024 bytes */ +static int get_packet_size(const unsigned char *buf, int size) +{ + int i; + + if (size < (TS_FEC_PACKET_SIZE * 5 + 1)) + return -1; + for(i=0;i<5;i++) { + if (buf[i * TS_PACKET_SIZE] != 0x47) + goto try_fec; + } + return TS_PACKET_SIZE; + try_fec: + for(i=0;i<5;i++) { + if (buf[i * TS_FEC_PACKET_SIZE] != 0x47) + return -1; + } + return TS_FEC_PACKET_SIZE; +} + +static int mpegts_probe(AVProbeData *p) +{ + int size; + size = get_packet_size(p->buf, p->buf_size); + if (size < 0) + return 0; + return AVPROBE_SCORE_MAX; +} + +static int mpegts_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + MpegTSContext *ts = s->priv_data; + ByteIOContext *pb = &s->pb; + unsigned char buf[1024]; + int len; + INT64 pos; + + /* read the first 1024 bytes to get packet size */ + pos = url_ftell(pb); + len = get_buffer(pb, buf, sizeof(buf)); + if (len != sizeof(buf)) + goto fail; + ts->raw_packet_size = get_packet_size(buf, sizeof(buf)); + if (ts->raw_packet_size <= 0) + goto fail; + /* go again to the start */ + url_fseek(pb, pos, SEEK_SET); + return 0; + fail: + return -1; +} + +/* return non zero if a packet could be constructed */ +static int mpegts_push_data(AVFormatContext *s, MpegTSStream *tss, + AVPacket *pkt, + const unsigned char *buf, int buf_size, int is_start) +{ + AVStream *st; + const unsigned char *p; + int len, code, codec_type, codec_id; + + if (is_start) { + tss->state = MPEGTS_HEADER; + tss->header_size = 0; + } + p = buf; + while (buf_size > 0) { + len = buf_size; + switch(tss->state) { + case MPEGTS_HEADER: + if (len > MAX_HEADER_SIZE - tss->header_size) + len = MAX_HEADER_SIZE - tss->header_size; + memcpy(tss->header, p, len); + tss->header_size += len; + p += len; + buf_size -= len; + if (tss->header_size == MAX_HEADER_SIZE) { + /* we got all the PES or section header. We can now + decide */ +#if 0 + av_hex_dump(tss->header, tss->header_size); +#endif + if (tss->header[0] == 0x00 && tss->header[1] == 0x00 && + tss->header[2] == 0x01) { + /* it must be an mpeg2 PES stream */ + /* XXX: add AC3 support */ + code = tss->header[3] | 0x100; + if (!((code >= 0x1c0 && code <= 0x1df) || + (code >= 0x1e0 && code <= 0x1ef))) + goto skip; + if (!tss->st) { + /* allocate stream */ + if (code >= 0x1c0 && code <= 0x1df) { + codec_type = CODEC_TYPE_AUDIO; + codec_id = CODEC_ID_MP2; + } else { + codec_type = CODEC_TYPE_VIDEO; + codec_id = CODEC_ID_MPEG1VIDEO; + } + st = av_new_stream(s, tss->pid); + if (st) { + st->priv_data = tss; + st->codec.codec_type = codec_type; + st->codec.codec_id = codec_id; + tss->st = st; + } + } + tss->state = MPEGTS_PESHEADER_FILL; + tss->payload_size = (tss->header[4] << 8) | tss->header[5]; + if (tss->payload_size == 0) + tss->payload_size = 65536; + } else { + /* otherwise, it should be a table */ + /* skip packet */ + skip: + tss->state = MPEGTS_SKIP; + continue; + } + } + break; + /**********************************************/ + /* PES packing parsing */ + case MPEGTS_PESHEADER_FILL: + /* skip filling */ + code = *p++; + buf_size--; + tss->payload_size--; + if (code != 0xff) { + if ((code & 0xc0) != 0x80) + goto skip; + tss->state = MPEGTS_PESHEADER_FLAGS; + } + break; + case MPEGTS_PESHEADER_FLAGS: + code = *p++; + buf_size--; + tss->payload_size--; + tss->state = MPEGTS_PESHEADER_SIZE; + break; + case MPEGTS_PESHEADER_SIZE: + tss->pes_header_size = *p++; + buf_size--; + tss->payload_size--; + tss->state = MPEGTS_PESHEADER_READ; + break; + case MPEGTS_PESHEADER_READ: + /* currently we do nothing except skipping */ + if (len > tss->pes_header_size) + len = tss->pes_header_size; + p += len; + buf_size -= len; + tss->pes_header_size -= len; + tss->payload_size -= len; + if (tss->pes_header_size == 0) + tss->state = MPEGTS_PAYLOAD; + break; + case MPEGTS_PAYLOAD: + if (len > tss->payload_size) + len = tss->payload_size; + if (len > 0) { + if (tss->st && av_new_packet(pkt, buf_size) == 0) { + memcpy(pkt->data, p, buf_size); + pkt->stream_index = tss->st->index; + return 1; + } + tss->payload_size -= len; + } + buf_size = 0; + break; + case MPEGTS_SKIP: + buf_size = 0; + break; + } + } + return 0; +} + +static int mpegts_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + MpegTSContext *ts = s->priv_data; + MpegTSStream *tss; + ByteIOContext *pb = &s->pb; + unsigned char packet[TS_FEC_PACKET_SIZE]; + int len, pid, cc, cc_ok, afc; + const unsigned char *p; + + for(;;) { + len = get_buffer(pb, packet, ts->raw_packet_size); + if (len != ts->raw_packet_size) + return AVERROR_IO; + /* check paquet sync byte */ + /* XXX: accept to resync ? */ + if (packet[0] != 0x47) + return AVERROR_INVALIDDATA; + + pid = ((packet[1] & 0x1f) << 8) | packet[2]; + tss = ts->pids[pid]; + if (tss == NULL) { + /* if no pid found, then add a pid context */ + tss = av_mallocz(sizeof(MpegTSStream)); + if (!tss) + continue; + ts->pids[pid] = tss; + tss->pid = pid; + tss->last_cc = -1; + // printf("new pid=0x%x\n", pid); + } + + /* continuity check (currently not used) */ + cc = (packet[3] & 0xf); + cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc)); + tss->last_cc = cc; + + /* skip adaptation field */ + afc = (packet[3] >> 4) & 3; + p = packet + 4; + if (afc == 0) /* reserved value */ + continue; + if (afc == 2) /* adaptation field only */ + continue; + if (afc == 3) { + /* skip adapation field */ + p += p[0] + 1; + } + /* if past the end of packet, ignore */ + if (p >= packet + TS_PACKET_SIZE) + continue; + + if (mpegts_push_data(s, tss, pkt, p, TS_PACKET_SIZE - (p - packet), + packet[1] & 0x40)) + break; + } + return 0; +} + +static int mpegts_read_close(AVFormatContext *s) +{ + MpegTSContext *ts = s->priv_data; + int i; + for(i=0;i<NB_PID_MAX;i++) + av_free(ts->pids[i]); + return 0; +} + +AVInputFormat mpegts_demux = { + "mpegts", + "MPEG2 transport stream format", + sizeof(MpegTSContext), + mpegts_probe, + mpegts_read_header, + mpegts_read_packet, + mpegts_read_close, + .flags = AVFMT_NOHEADER | AVFMT_SHOW_IDS, +}; + +int mpegts_init(void) +{ + av_register_input_format(&mpegts_demux); + return 0; +} diff --git a/libavformat/ogg.c b/libavformat/ogg.c new file mode 100644 index 0000000000..7c7c96c61f --- /dev/null +++ b/libavformat/ogg.c @@ -0,0 +1,269 @@ +/* + * Ogg bitstream support + * Mark Hills <mark@pogo.org.uk> + * + * Uses libogg, but requires libvorbisenc to construct correct headers + * when containing Vorbis stream -- currently the only format supported + */ + +#include <stdio.h> +#include <time.h> + +#include <ogg/ogg.h> +#include <vorbis/vorbisenc.h> + +#include "avformat.h" +#include "oggvorbis.h" + +#define DECODER_BUFFER_SIZE 4096 + + +typedef struct OggContext { + /* output */ + ogg_stream_state os ; + int header_handled ; + ogg_int64_t base_packet_no ; + ogg_int64_t base_granule_pos ; + + /* input */ + ogg_sync_state oy ; +} OggContext ; + + +static int ogg_write_header(AVFormatContext *avfcontext) { + OggContext *context ; + AVCodecContext *avccontext ; + vorbis_info vi ; + vorbis_dsp_state vd ; + vorbis_comment vc ; + vorbis_block vb ; + ogg_packet header, header_comm, header_code ; + int n ; + + if(!(context = malloc(sizeof(OggContext)))) + return -1 ; + avfcontext->priv_data = context ; + + srand(time(NULL)); + ogg_stream_init(&context->os, rand()); + + for(n = 0 ; n < avfcontext->nb_streams ; n++) { + avccontext = &avfcontext->streams[n]->codec ; + + /* begin vorbis specific code */ + + vorbis_info_init(&vi) ; + + /* code copied from libavcodec/oggvorbis.c */ + + if(oggvorbis_init_encoder(&vi, avccontext) < 0) { + fprintf(stderr, "ogg_write_header: init_encoder failed") ; + return -1 ; + } + + vorbis_analysis_init(&vd, &vi) ; + vorbis_block_init(&vd, &vb) ; + + vorbis_comment_init(&vc) ; + vorbis_comment_add_tag(&vc, "encoder", "ffmpeg") ; + if(*avfcontext->title) + vorbis_comment_add_tag(&vc, "title", avfcontext->title) ; + + vorbis_analysis_headerout(&vd, &vc, &header, + &header_comm, &header_code) ; + ogg_stream_packetin(&context->os, &header) ; + ogg_stream_packetin(&context->os, &header_comm) ; + ogg_stream_packetin(&context->os, &header_code) ; + + vorbis_comment_clear(&vc) ; + + /* end of vorbis specific code */ + + context->header_handled = 0 ; + context->base_packet_no = 0 ; + } + + return 0 ; +} + + +static int ogg_write_packet(AVFormatContext *avfcontext, + int stream_index, + unsigned char *buf, int size, int force_pts) +{ + OggContext *context = avfcontext->priv_data ; + ogg_packet *op ; + ogg_page og ; + int l = 0 ; + + /* flush header packets so audio starts on a new page */ + + if(!context->header_handled) { + while(ogg_stream_flush(&context->os, &og)) { + put_buffer(&avfcontext->pb, og.header, og.header_len) ; + put_buffer(&avfcontext->pb, og.body, og.body_len) ; + put_flush_packet(&avfcontext->pb); + } + context->header_handled = 1 ; + } + + while(l < size) { + op = (ogg_packet*)(buf + l) ; + op->packet = buf + l + sizeof(ogg_packet) ; /* fix data pointer */ + + if(!context->base_packet_no) { /* this is the first packet */ + context->base_packet_no = op->packetno ; + context->base_granule_pos = op->granulepos ; + } + + /* correct the fields in the packet -- essential for streaming */ + + op->packetno -= context->base_packet_no ; + op->granulepos -= context->base_granule_pos ; + + ogg_stream_packetin(&context->os, op) ; + l += sizeof(ogg_packet) + op->bytes ; + + while(ogg_stream_pageout(&context->os, &og)) { + put_buffer(&avfcontext->pb, og.header, og.header_len) ; + put_buffer(&avfcontext->pb, og.body, og.body_len) ; + put_flush_packet(&avfcontext->pb); + } + } + + return 0; +} + + +static int ogg_write_trailer(AVFormatContext *avfcontext) { + OggContext *context = avfcontext->priv_data ; + ogg_page og ; + + while(ogg_stream_flush(&context->os, &og)) { + put_buffer(&avfcontext->pb, og.header, og.header_len) ; + put_buffer(&avfcontext->pb, og.body, og.body_len) ; + put_flush_packet(&avfcontext->pb); + } + + ogg_stream_clear(&context->os) ; + return 0 ; +} + + +static AVOutputFormat ogg_oformat = { + "ogg", + "Ogg Vorbis", + "audio/x-vorbis", + "ogg", + sizeof(OggContext), + CODEC_ID_VORBIS, + 0, + ogg_write_header, + ogg_write_packet, + ogg_write_trailer, +} ; + + +static int next_packet(AVFormatContext *avfcontext, ogg_packet *op) { + OggContext *context = avfcontext->priv_data ; + ogg_page og ; + char *buf ; + + while(ogg_stream_packetout(&context->os, op) != 1) { + + /* while no pages are available, read in more data to the sync */ + while(ogg_sync_pageout(&context->oy, &og) != 1) { + buf = ogg_sync_buffer(&context->oy, DECODER_BUFFER_SIZE) ; + if(get_buffer(&avfcontext->pb, buf, DECODER_BUFFER_SIZE) <= 0) + return 1 ; + ogg_sync_wrote(&context->oy, DECODER_BUFFER_SIZE) ; + } + + /* got a page. Feed it into the stream and get the packet */ + if(ogg_stream_pagein(&context->os, &og) != 0) + return 1 ; + } + + return 0 ; +} + + +static int ogg_read_header(AVFormatContext *avfcontext, AVFormatParameters *ap) +{ + OggContext *context ; + char *buf ; + ogg_page og ; + AVStream *ast ; + + if(!(context = malloc(sizeof(OggContext)))) { + perror("malloc") ; + return -1 ; + } + avfcontext->priv_data = context ; + + ogg_sync_init(&context->oy) ; + buf = ogg_sync_buffer(&context->oy, DECODER_BUFFER_SIZE) ; + + if(get_buffer(&avfcontext->pb, buf, DECODER_BUFFER_SIZE) <= 0) + return -EIO ; + + ogg_sync_wrote(&context->oy, DECODER_BUFFER_SIZE) ; + ogg_sync_pageout(&context->oy, &og) ; + ogg_stream_init(&context->os, ogg_page_serialno(&og)) ; + ogg_stream_pagein(&context->os, &og) ; + + /* currently only one vorbis stream supported */ + + ast = av_new_stream(avfcontext, 0) ; + if(!ast) + return AVERROR_NOMEM ; + + ast->codec.codec_type = CODEC_TYPE_AUDIO ; + ast->codec.codec_id = CODEC_ID_VORBIS ; + + return 0 ; +} + + +static int ogg_read_packet(AVFormatContext *avfcontext, AVPacket *pkt) { + ogg_packet op ; + + if(next_packet(avfcontext, &op)) + return -EIO ; + if(av_new_packet(pkt, sizeof(ogg_packet) + op.bytes) < 0) + return -EIO ; + pkt->stream_index = 0 ; + memcpy(pkt->data, &op, sizeof(ogg_packet)) ; + memcpy(pkt->data + sizeof(ogg_packet), op.packet, op.bytes) ; + + return sizeof(ogg_packet) + op.bytes ; +} + + +static int ogg_read_close(AVFormatContext *avfcontext) { + OggContext *context = avfcontext->priv_data ; + + ogg_stream_clear(&context->os) ; + ogg_sync_clear(&context->oy) ; + + return 0 ; +} + + +static AVInputFormat ogg_iformat = { + "ogg", + "Ogg Vorbis", + sizeof(OggContext), + NULL, + ogg_read_header, + ogg_read_packet, + ogg_read_close, + .extensions = "ogg", +} ; + + +int ogg_init(void) { + av_register_output_format(&ogg_oformat) ; + av_register_input_format(&ogg_iformat); + return 0 ; +} diff --git a/libavformat/raw.c b/libavformat/raw.c new file mode 100644 index 0000000000..518206ea56 --- /dev/null +++ b/libavformat/raw.c @@ -0,0 +1,509 @@ +/* + * RAW encoder and decoder + * Copyright (c) 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* simple formats */ +int raw_write_header(struct AVFormatContext *s) +{ + return 0; +} + +int raw_write_packet(struct AVFormatContext *s, + int stream_index, + unsigned char *buf, int size, int force_pts) +{ + put_buffer(&s->pb, buf, size); + put_flush_packet(&s->pb); + return 0; +} + +int raw_write_trailer(struct AVFormatContext *s) +{ + return 0; +} + +/* raw input */ +static int raw_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + AVStream *st; + int id; + + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + if (ap) { + id = s->iformat->value; + if (id == CODEC_ID_RAWVIDEO) { + st->codec.codec_type = CODEC_TYPE_VIDEO; + } else { + st->codec.codec_type = CODEC_TYPE_AUDIO; + } + st->codec.codec_id = id; + + switch(st->codec.codec_type) { + case CODEC_TYPE_AUDIO: + st->codec.sample_rate = ap->sample_rate; + st->codec.channels = ap->channels; + break; + case CODEC_TYPE_VIDEO: + st->codec.frame_rate = ap->frame_rate; + st->codec.width = ap->width; + st->codec.height = ap->height; + break; + default: + return -1; + } + } else { + return -1; + } + return 0; +} + +#define RAW_PACKET_SIZE 1024 + +int raw_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + int ret, size; + AVStream *st = s->streams[0]; + + size= RAW_PACKET_SIZE; + + if (av_new_packet(pkt, size) < 0) + return -EIO; + + pkt->stream_index = 0; + ret = get_buffer(&s->pb, pkt->data, size); + if (ret <= 0) { + av_free_packet(pkt); + return -EIO; + } + /* note: we need to modify the packet size here to handle the last + packet */ + pkt->size = ret; + return ret; +} + +int raw_read_close(AVFormatContext *s) +{ + return 0; +} + +/* mp3 read */ +static int mp3_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + AVStream *st; + + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + + st->codec.codec_type = CODEC_TYPE_AUDIO; + st->codec.codec_id = CODEC_ID_MP2; + /* the parameters will be extracted from the compressed bitstream */ + return 0; +} + +/* mpeg1/h263 input */ +static int video_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + AVStream *st; + + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + + st->codec.codec_type = CODEC_TYPE_VIDEO; + st->codec.codec_id = s->iformat->value; + /* for mjpeg, specify frame rate */ + /* for mpeg4 specify it too (most mpeg4 streams dont have the fixed_vop_rate set ...)*/ + if (st->codec.codec_id == CODEC_ID_MJPEG || st->codec.codec_id == CODEC_ID_MPEG4) { + if (ap) { + st->codec.frame_rate = ap->frame_rate; + } else { + st->codec.frame_rate = 25 * FRAME_RATE_BASE; + } + } + return 0; +} + +#define SEQ_START_CODE 0x000001b3 +#define GOP_START_CODE 0x000001b8 +#define PICTURE_START_CODE 0x00000100 + +/* XXX: improve that by looking at several start codes */ +static int mpegvideo_probe(AVProbeData *p) +{ + int code, c, i; + code = 0xff; + + /* we search the first start code. If it is a sequence, gop or + picture start code then we decide it is an mpeg video + stream. We do not send highest value to give a chance to mpegts */ + for(i=0;i<p->buf_size;i++) { + c = p->buf[i]; + code = (code << 8) | c; + if ((code & 0xffffff00) == 0x100) { + if (code == SEQ_START_CODE || + code == GOP_START_CODE || + code == PICTURE_START_CODE) + return 50 - 1; + else + return 0; + } + } + return 0; +} + +AVInputFormat mp3_iformat = { + "mp3", + "MPEG audio", + 0, + NULL, + mp3_read_header, + raw_read_packet, + raw_read_close, + .extensions = "mp2,mp3", /* XXX: use probe */ +}; + +AVOutputFormat mp2_oformat = { + "mp2", + "MPEG audio layer 2", + "audio/x-mpeg", + "mp2,mp3", + 0, + CODEC_ID_MP2, + 0, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + + +AVInputFormat ac3_iformat = { + "ac3", + "raw ac3", + 0, + NULL, + raw_read_header, + raw_read_packet, + raw_read_close, + .extensions = "ac3", + .value = CODEC_ID_AC3, +}; + +AVOutputFormat ac3_oformat = { + "ac3", + "raw ac3", + "audio/x-ac3", + "ac3", + 0, + CODEC_ID_AC3, + 0, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +AVOutputFormat h263_oformat = { + "h263", + "raw h263", + "video/x-h263", + "h263", + 0, + 0, + CODEC_ID_H263, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +AVInputFormat m4v_iformat = { + "m4v", + "raw MPEG4 video format", + 0, + NULL /*mpegvideo_probe*/, + video_read_header, + raw_read_packet, + raw_read_close, + .extensions = "m4v", //FIXME remove after writing mpeg4_probe + .value = CODEC_ID_MPEG4, +}; + +AVOutputFormat m4v_oformat = { + "m4v", + "raw MPEG4 video format", + NULL, + "m4v", + 0, + CODEC_ID_NONE, + CODEC_ID_MPEG4, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +AVInputFormat mpegvideo_iformat = { + "mpegvideo", + "MPEG video", + 0, + mpegvideo_probe, + video_read_header, + raw_read_packet, + raw_read_close, + .value = CODEC_ID_MPEG1VIDEO, +}; + +AVOutputFormat mpeg1video_oformat = { + "mpeg1video", + "MPEG video", + "video/x-mpeg", + "mpg,mpeg", + 0, + 0, + CODEC_ID_MPEG1VIDEO, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +AVInputFormat mjpeg_iformat = { + "mjpeg", + "MJPEG video", + 0, + NULL, + video_read_header, + raw_read_packet, + raw_read_close, + .extensions = "mjpg,mjpeg", + .value = CODEC_ID_MJPEG, +}; + +AVOutputFormat mjpeg_oformat = { + "mjpeg", + "MJPEG video", + "video/x-mjpeg", + "mjpg,mjpeg", + 0, + 0, + CODEC_ID_MJPEG, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +/* pcm formats */ + +#define PCMDEF(name, long_name, ext, codec) \ +AVInputFormat pcm_ ## name ## _iformat = {\ + #name,\ + long_name,\ + 0,\ + NULL,\ + raw_read_header,\ + raw_read_packet,\ + raw_read_close,\ + .extensions = ext,\ + .value = codec,\ +};\ +\ +AVOutputFormat pcm_ ## name ## _oformat = {\ + #name,\ + long_name,\ + NULL,\ + ext,\ + 0,\ + codec,\ + 0,\ + raw_write_header,\ + raw_write_packet,\ + raw_write_trailer,\ +}; + +#ifdef WORDS_BIGENDIAN +#define BE_DEF(s) s +#define LE_DEF(s) NULL +#else +#define BE_DEF(s) NULL +#define LE_DEF(s) s +#endif + + +PCMDEF(s16le, "pcm signed 16 bit little endian format", + LE_DEF("sw"), CODEC_ID_PCM_S16LE) + +PCMDEF(s16be, "pcm signed 16 bit big endian format", + BE_DEF("sw"), CODEC_ID_PCM_S16BE) + +PCMDEF(u16le, "pcm unsigned 16 bit little endian format", + LE_DEF("uw"), CODEC_ID_PCM_U16LE) + +PCMDEF(u16be, "pcm unsigned 16 bit big endian format", + BE_DEF("uw"), CODEC_ID_PCM_U16BE) + +PCMDEF(s8, "pcm signed 8 bit format", + "sb", CODEC_ID_PCM_S8) + +PCMDEF(u8, "pcm unsigned 8 bit format", + "ub", CODEC_ID_PCM_U8) + +PCMDEF(mulaw, "pcm mu law format", + "ul", CODEC_ID_PCM_MULAW) + +PCMDEF(alaw, "pcm A law format", + "al", CODEC_ID_PCM_ALAW) + +int rawvideo_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + int packet_size, ret, width, height; + AVStream *st = s->streams[0]; + + width = st->codec.width; + height = st->codec.height; + + switch(st->codec.pix_fmt) { + case PIX_FMT_YUV420P: + packet_size = (width * height * 3) / 2; + break; + case PIX_FMT_YUV422: + packet_size = (width * height * 2); + break; + case PIX_FMT_BGR24: + case PIX_FMT_RGB24: + packet_size = (width * height * 3); + break; + default: + av_abort(); + break; + } + + if (av_new_packet(pkt, packet_size) < 0) + return -EIO; + + pkt->stream_index = 0; +#if 0 + /* bypass buffered I/O */ + ret = url_read(url_fileno(&s->pb), pkt->data, pkt->size); +#else + ret = get_buffer(&s->pb, pkt->data, pkt->size); +#endif + if (ret != pkt->size) { + av_free_packet(pkt); + return -EIO; + } else { + return 0; + } +} + +AVInputFormat rawvideo_iformat = { + "rawvideo", + "raw video format", + 0, + NULL, + raw_read_header, + rawvideo_read_packet, + raw_read_close, + .extensions = "yuv", + .value = CODEC_ID_RAWVIDEO, +}; + +AVOutputFormat rawvideo_oformat = { + "rawvideo", + "raw video format", + NULL, + "yuv", + 0, + CODEC_ID_NONE, + CODEC_ID_RAWVIDEO, + raw_write_header, + raw_write_packet, + raw_write_trailer, +}; + +static int null_write_packet(struct AVFormatContext *s, + int stream_index, + unsigned char *buf, int size, int force_pts) +{ + return 0; +} + +AVOutputFormat null_oformat = { + "null", + "null video format", + NULL, + NULL, + 0, +#ifdef WORDS_BIGENDIAN + CODEC_ID_PCM_S16BE, +#else + CODEC_ID_PCM_S16LE, +#endif + CODEC_ID_RAWVIDEO, + raw_write_header, + null_write_packet, + raw_write_trailer, + .flags = AVFMT_NOFILE | AVFMT_RAWPICTURE, +}; + +int raw_init(void) +{ + av_register_input_format(&mp3_iformat); + av_register_output_format(&mp2_oformat); + + av_register_input_format(&ac3_iformat); + av_register_output_format(&ac3_oformat); + + av_register_output_format(&h263_oformat); + + av_register_input_format(&m4v_iformat); + av_register_output_format(&m4v_oformat); + + av_register_input_format(&mpegvideo_iformat); + av_register_output_format(&mpeg1video_oformat); + + av_register_input_format(&mjpeg_iformat); + av_register_output_format(&mjpeg_oformat); + + av_register_input_format(&pcm_s16le_iformat); + av_register_output_format(&pcm_s16le_oformat); + av_register_input_format(&pcm_s16be_iformat); + av_register_output_format(&pcm_s16be_oformat); + av_register_input_format(&pcm_u16le_iformat); + av_register_output_format(&pcm_u16le_oformat); + av_register_input_format(&pcm_u16be_iformat); + av_register_output_format(&pcm_u16be_oformat); + av_register_input_format(&pcm_s8_iformat); + av_register_output_format(&pcm_s8_oformat); + av_register_input_format(&pcm_u8_iformat); + av_register_output_format(&pcm_u8_oformat); + av_register_input_format(&pcm_mulaw_iformat); + av_register_output_format(&pcm_mulaw_oformat); + av_register_input_format(&pcm_alaw_iformat); + av_register_output_format(&pcm_alaw_oformat); + + av_register_input_format(&rawvideo_iformat); + av_register_output_format(&rawvideo_oformat); + + av_register_output_format(&null_oformat); + return 0; +} diff --git a/libavformat/rm.c b/libavformat/rm.c new file mode 100644 index 0000000000..be90a27c89 --- /dev/null +++ b/libavformat/rm.c @@ -0,0 +1,773 @@ +/* + * "Real" compatible mux and demux. + * Copyright (c) 2000, 2001 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* in ms */ +#define BUFFER_DURATION 0 + +typedef struct { + int nb_packets; + int packet_total_size; + int packet_max_size; + /* codec related output */ + int bit_rate; + float frame_rate; + int nb_frames; /* current frame number */ + int total_frames; /* total number of frames */ + int num; + AVCodecContext *enc; +} StreamInfo; + +typedef struct { + StreamInfo streams[2]; + StreamInfo *audio_stream, *video_stream; + int data_pos; /* position of the data after the header */ + int nb_packets; +} RMContext; + +static void put_str(ByteIOContext *s, const char *tag) +{ + put_be16(s,strlen(tag)); + while (*tag) { + put_byte(s, *tag++); + } +} + +static void put_str8(ByteIOContext *s, const char *tag) +{ + put_byte(s, strlen(tag)); + while (*tag) { + put_byte(s, *tag++); + } +} + +static void rv10_write_header(AVFormatContext *ctx, + int data_size, int index_pos) +{ + RMContext *rm = ctx->priv_data; + ByteIOContext *s = &ctx->pb; + StreamInfo *stream; + unsigned char *data_offset_ptr, *start_ptr; + const char *desc, *mimetype; + int nb_packets, packet_total_size, packet_max_size, size, packet_avg_size, i; + int bit_rate, v, duration, flags, data_pos; + + start_ptr = s->buf_ptr; + + put_tag(s, ".RMF"); + put_be32(s,18); /* header size */ + put_be16(s,0); + put_be32(s,0); + put_be32(s,4 + ctx->nb_streams); /* num headers */ + + put_tag(s,"PROP"); + put_be32(s, 50); + put_be16(s, 0); + packet_max_size = 0; + packet_total_size = 0; + nb_packets = 0; + bit_rate = 0; + duration = 0; + for(i=0;i<ctx->nb_streams;i++) { + StreamInfo *stream = &rm->streams[i]; + bit_rate += stream->bit_rate; + if (stream->packet_max_size > packet_max_size) + packet_max_size = stream->packet_max_size; + nb_packets += stream->nb_packets; + packet_total_size += stream->packet_total_size; + /* select maximum duration */ + v = (int) (1000.0 * (float)stream->total_frames / stream->frame_rate); + if (v > duration) + duration = v; + } + put_be32(s, bit_rate); /* max bit rate */ + put_be32(s, bit_rate); /* avg bit rate */ + put_be32(s, packet_max_size); /* max packet size */ + if (nb_packets > 0) + packet_avg_size = packet_total_size / nb_packets; + else + packet_avg_size = 0; + put_be32(s, packet_avg_size); /* avg packet size */ + put_be32(s, nb_packets); /* num packets */ + put_be32(s, duration); /* duration */ + put_be32(s, BUFFER_DURATION); /* preroll */ + put_be32(s, index_pos); /* index offset */ + /* computation of data the data offset */ + data_offset_ptr = s->buf_ptr; + put_be32(s, 0); /* data offset : will be patched after */ + put_be16(s, ctx->nb_streams); /* num streams */ + flags = 1 | 2; /* save allowed & perfect play */ + if (url_is_streamed(s)) + flags |= 4; /* live broadcast */ + put_be16(s, flags); + + /* comments */ + + put_tag(s,"CONT"); + size = strlen(ctx->title) + strlen(ctx->author) + strlen(ctx->copyright) + + strlen(ctx->comment) + 4 * 2 + 10; + put_be32(s,size); + put_be16(s,0); + put_str(s, ctx->title); + put_str(s, ctx->author); + put_str(s, ctx->copyright); + put_str(s, ctx->comment); + + for(i=0;i<ctx->nb_streams;i++) { + int codec_data_size; + + stream = &rm->streams[i]; + + if (stream->enc->codec_type == CODEC_TYPE_AUDIO) { + desc = "The Audio Stream"; + mimetype = "audio/x-pn-realaudio"; + codec_data_size = 73; + } else { + desc = "The Video Stream"; + mimetype = "video/x-pn-realvideo"; + codec_data_size = 34; + } + + put_tag(s,"MDPR"); + size = 10 + 9 * 4 + strlen(desc) + strlen(mimetype) + codec_data_size; + put_be32(s, size); + put_be16(s, 0); + + put_be16(s, i); /* stream number */ + put_be32(s, stream->bit_rate); /* max bit rate */ + put_be32(s, stream->bit_rate); /* avg bit rate */ + put_be32(s, stream->packet_max_size); /* max packet size */ + if (stream->nb_packets > 0) + packet_avg_size = stream->packet_total_size / + stream->nb_packets; + else + packet_avg_size = 0; + put_be32(s, packet_avg_size); /* avg packet size */ + put_be32(s, 0); /* start time */ + put_be32(s, BUFFER_DURATION); /* preroll */ + /* duration */ + if (url_is_streamed(s) || !stream->total_frames) + put_be32(s, (int)(3600 * 1000)); + else + put_be32(s, (int)(stream->total_frames * 1000 / stream->frame_rate)); + put_str8(s, desc); + put_str8(s, mimetype); + put_be32(s, codec_data_size); + + if (stream->enc->codec_type == CODEC_TYPE_AUDIO) { + int coded_frame_size, fscode, sample_rate; + sample_rate = stream->enc->sample_rate; + coded_frame_size = (stream->enc->bit_rate * + stream->enc->frame_size) / (8 * sample_rate); + /* audio codec info */ + put_tag(s, ".ra"); + put_byte(s, 0xfd); + put_be32(s, 0x00040000); /* version */ + put_tag(s, ".ra4"); + put_be32(s, 0x01b53530); /* stream length */ + put_be16(s, 4); /* unknown */ + put_be32(s, 0x39); /* header size */ + + switch(sample_rate) { + case 48000: + case 24000: + case 12000: + fscode = 1; + break; + default: + case 44100: + case 22050: + case 11025: + fscode = 2; + break; + case 32000: + case 16000: + case 8000: + fscode = 3; + } + put_be16(s, fscode); /* codec additional info, for AC3, seems + to be a frequency code */ + /* special hack to compensate rounding errors... */ + if (coded_frame_size == 557) + coded_frame_size--; + put_be32(s, coded_frame_size); /* frame length */ + put_be32(s, 0x51540); /* unknown */ + put_be32(s, 0x249f0); /* unknown */ + put_be32(s, 0x249f0); /* unknown */ + put_be16(s, 0x01); + /* frame length : seems to be very important */ + put_be16(s, coded_frame_size); + put_be32(s, 0); /* unknown */ + put_be16(s, stream->enc->sample_rate); /* sample rate */ + put_be32(s, 0x10); /* unknown */ + put_be16(s, stream->enc->channels); + put_str8(s, "Int0"); /* codec name */ + put_str8(s, "dnet"); /* codec name */ + put_be16(s, 0); /* title length */ + put_be16(s, 0); /* author length */ + put_be16(s, 0); /* copyright length */ + put_byte(s, 0); /* end of header */ + } else { + /* video codec info */ + put_be32(s,34); /* size */ + put_tag(s,"VIDORV10"); + put_be16(s, stream->enc->width); + put_be16(s, stream->enc->height); + put_be16(s, (int) stream->frame_rate); /* frames per seconds ? */ + put_be32(s,0); /* unknown meaning */ + put_be16(s, (int) stream->frame_rate); /* unknown meaning */ + put_be32(s,0); /* unknown meaning */ + put_be16(s, 8); /* unknown meaning */ + /* Seems to be the codec version: only use basic H263. The next + versions seems to add a diffential DC coding as in + MPEG... nothing new under the sun */ + put_be32(s,0x10000000); + //put_be32(s,0x10003000); + } + } + + /* patch data offset field */ + data_pos = s->buf_ptr - start_ptr; + rm->data_pos = data_pos; + data_offset_ptr[0] = data_pos >> 24; + data_offset_ptr[1] = data_pos >> 16; + data_offset_ptr[2] = data_pos >> 8; + data_offset_ptr[3] = data_pos; + + /* data stream */ + put_tag(s,"DATA"); + put_be32(s,data_size + 10 + 8); + put_be16(s,0); + + put_be32(s, nb_packets); /* number of packets */ + put_be32(s,0); /* next data header */ +} + +static void write_packet_header(AVFormatContext *ctx, StreamInfo *stream, + int length, int key_frame) +{ + int timestamp; + ByteIOContext *s = &ctx->pb; + + stream->nb_packets++; + stream->packet_total_size += length; + if (length > stream->packet_max_size) + stream->packet_max_size = length; + + put_be16(s,0); /* version */ + put_be16(s,length + 12); + put_be16(s, stream->num); /* stream number */ + timestamp = (1000 * (float)stream->nb_frames) / stream->frame_rate; + put_be32(s, timestamp); /* timestamp */ + put_byte(s, 0); /* reserved */ + put_byte(s, key_frame ? 2 : 0); /* flags */ +} + +static int rm_write_header(AVFormatContext *s) +{ + RMContext *rm = s->priv_data; + StreamInfo *stream; + int n; + AVCodecContext *codec; + + for(n=0;n<s->nb_streams;n++) { + s->streams[n]->id = n; + codec = &s->streams[n]->codec; + stream = &rm->streams[n]; + memset(stream, 0, sizeof(StreamInfo)); + stream->num = n; + stream->bit_rate = codec->bit_rate; + stream->enc = codec; + + switch(codec->codec_type) { + case CODEC_TYPE_AUDIO: + rm->audio_stream = stream; + stream->frame_rate = (float)codec->sample_rate / (float)codec->frame_size; + /* XXX: dummy values */ + stream->packet_max_size = 1024; + stream->nb_packets = 0; + stream->total_frames = stream->nb_packets; + break; + case CODEC_TYPE_VIDEO: + rm->video_stream = stream; + stream->frame_rate = (float)codec->frame_rate / (float)FRAME_RATE_BASE; + /* XXX: dummy values */ + stream->packet_max_size = 4096; + stream->nb_packets = 0; + stream->total_frames = stream->nb_packets; + break; + default: + av_abort(); + } + } + + rv10_write_header(s, 0, 0); + put_flush_packet(&s->pb); + return 0; +} + +static int rm_write_audio(AVFormatContext *s, UINT8 *buf, int size) +{ + UINT8 *buf1; + RMContext *rm = s->priv_data; + ByteIOContext *pb = &s->pb; + StreamInfo *stream = rm->audio_stream; + int i; + + /* XXX: suppress this malloc */ + buf1= (UINT8*) av_malloc( size * sizeof(UINT8) ); + + write_packet_header(s, stream, size, stream->enc->key_frame); + + /* for AC3, the words seems to be reversed */ + for(i=0;i<size;i+=2) { + buf1[i] = buf[i+1]; + buf1[i+1] = buf[i]; + } + put_buffer(pb, buf1, size); + put_flush_packet(pb); + stream->nb_frames++; + av_free(buf1); + return 0; +} + +static int rm_write_video(AVFormatContext *s, UINT8 *buf, int size) +{ + RMContext *rm = s->priv_data; + ByteIOContext *pb = &s->pb; + StreamInfo *stream = rm->video_stream; + int key_frame = stream->enc->key_frame; + + /* XXX: this is incorrect: should be a parameter */ + + /* Well, I spent some time finding the meaning of these bits. I am + not sure I understood everything, but it works !! */ +#if 1 + write_packet_header(s, stream, size + 7, key_frame); + /* bit 7: '1' if final packet of a frame converted in several packets */ + put_byte(pb, 0x81); + /* bit 7: '1' if I frame. bits 6..0 : sequence number in current + frame starting from 1 */ + if (key_frame) { + put_byte(pb, 0x81); + } else { + put_byte(pb, 0x01); + } + put_be16(pb, 0x4000 | (size)); /* total frame size */ + put_be16(pb, 0x4000 | (size)); /* offset from the start or the end */ +#else + /* full frame */ + write_packet_header(s, size + 6); + put_byte(pb, 0xc0); + put_be16(pb, 0x4000 | size); /* total frame size */ + put_be16(pb, 0x4000 + packet_number * 126); /* position in stream */ +#endif + put_byte(pb, stream->nb_frames & 0xff); + + put_buffer(pb, buf, size); + put_flush_packet(pb); + + stream->nb_frames++; + return 0; +} + +static int rm_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + if (s->streams[stream_index]->codec.codec_type == + CODEC_TYPE_AUDIO) + return rm_write_audio(s, buf, size); + else + return rm_write_video(s, buf, size); +} + +static int rm_write_trailer(AVFormatContext *s) +{ + RMContext *rm = s->priv_data; + int data_size, index_pos, i; + ByteIOContext *pb = &s->pb; + + if (!url_is_streamed(&s->pb)) { + /* end of file: finish to write header */ + index_pos = url_fseek(pb, 0, SEEK_CUR); + data_size = index_pos - rm->data_pos; + + /* index */ + put_tag(pb, "INDX"); + put_be32(pb, 10 + 10 * s->nb_streams); + put_be16(pb, 0); + + for(i=0;i<s->nb_streams;i++) { + put_be32(pb, 0); /* zero indices */ + put_be16(pb, i); /* stream number */ + put_be32(pb, 0); /* next index */ + } + /* undocumented end header */ + put_be32(pb, 0); + put_be32(pb, 0); + + url_fseek(pb, 0, SEEK_SET); + for(i=0;i<s->nb_streams;i++) + rm->streams[i].total_frames = rm->streams[i].nb_frames; + rv10_write_header(s, data_size, index_pos); + } else { + /* undocumented end header */ + put_be32(pb, 0); + put_be32(pb, 0); + } + put_flush_packet(pb); + return 0; +} + +/***************************************************/ + +static void get_str(ByteIOContext *pb, char *buf, int buf_size) +{ + int len, i; + char *q; + + len = get_be16(pb); + q = buf; + for(i=0;i<len;i++) { + if (i < buf_size - 1) + *q++ = get_byte(pb); + } + *q = '\0'; +} + +static void get_str8(ByteIOContext *pb, char *buf, int buf_size) +{ + int len, i; + char *q; + + len = get_byte(pb); + q = buf; + for(i=0;i<len;i++) { + if (i < buf_size - 1) + *q++ = get_byte(pb); + } + *q = '\0'; +} + +static int rm_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + RMContext *rm = s->priv_data; + AVStream *st; + ByteIOContext *pb = &s->pb; + unsigned int tag, v; + int tag_size, size, codec_data_size, i; + INT64 codec_pos; + unsigned int h263_hack_version; + char buf[128]; + int flags = 0; + + if (get_le32(pb) != MKTAG('.', 'R', 'M', 'F')) + return -EIO; + + get_be32(pb); /* header size */ + get_be16(pb); + get_be32(pb); + get_be32(pb); /* number of headers */ + + for(;;) { + if (url_feof(pb)) + goto fail; + tag = get_le32(pb); + tag_size = get_be32(pb); + get_be16(pb); +#if 0 + printf("tag=%c%c%c%c (%08x) size=%d\n", + (tag) & 0xff, + (tag >> 8) & 0xff, + (tag >> 16) & 0xff, + (tag >> 24) & 0xff, + tag, + tag_size); +#endif + if (tag_size < 10) + goto fail; + switch(tag) { + case MKTAG('P', 'R', 'O', 'P'): + /* file header */ + get_be32(pb); /* max bit rate */ + get_be32(pb); /* avg bit rate */ + get_be32(pb); /* max packet size */ + get_be32(pb); /* avg packet size */ + get_be32(pb); /* nb packets */ + get_be32(pb); /* duration */ + get_be32(pb); /* preroll */ + get_be32(pb); /* index offset */ + get_be32(pb); /* data offset */ + get_be16(pb); /* nb streams */ + flags = get_be16(pb); /* flags */ + break; + case MKTAG('C', 'O', 'N', 'T'): + get_str(pb, s->title, sizeof(s->title)); + get_str(pb, s->author, sizeof(s->author)); + get_str(pb, s->copyright, sizeof(s->copyright)); + get_str(pb, s->comment, sizeof(s->comment)); + break; + case MKTAG('M', 'D', 'P', 'R'): + st = av_mallocz(sizeof(AVStream)); + if (!st) + goto fail; + s->streams[s->nb_streams++] = st; + st->id = get_be16(pb); + get_be32(pb); /* max bit rate */ + st->codec.bit_rate = get_be32(pb); /* bit rate */ + get_be32(pb); /* max packet size */ + get_be32(pb); /* avg packet size */ + get_be32(pb); /* start time */ + get_be32(pb); /* preroll */ + get_be32(pb); /* duration */ + get_str8(pb, buf, sizeof(buf)); /* desc */ + get_str8(pb, buf, sizeof(buf)); /* mimetype */ + codec_data_size = get_be32(pb); + codec_pos = url_ftell(pb); + + v = get_be32(pb); + if (v == MKTAG(0xfd, 'a', 'r', '.')) { + /* ra type header */ + get_be32(pb); /* version */ + get_be32(pb); /* .ra4 */ + get_be32(pb); + get_be16(pb); + get_be32(pb); /* header size */ + get_be16(pb); /* add codec info */ + get_be32(pb); /* coded frame size */ + get_be32(pb); /* ??? */ + get_be32(pb); /* ??? */ + get_be32(pb); /* ??? */ + get_be16(pb); /* 1 */ + get_be16(pb); /* coded frame size */ + get_be32(pb); + st->codec.sample_rate = get_be16(pb); + get_be32(pb); + st->codec.channels = get_be16(pb); + get_str8(pb, buf, sizeof(buf)); /* desc */ + get_str8(pb, buf, sizeof(buf)); /* desc */ + st->codec.codec_type = CODEC_TYPE_AUDIO; + if (!strcmp(buf, "dnet")) { + st->codec.codec_id = CODEC_ID_AC3; + } else { + st->codec.codec_id = CODEC_ID_NONE; + pstrcpy(st->codec.codec_name, sizeof(st->codec.codec_name), + buf); + } + } else { + if (get_le32(pb) != MKTAG('V', 'I', 'D', 'O')) { + fail1: + fprintf(stderr, "Unsupported video codec\n"); + goto fail; + } + st->codec.codec_tag = get_le32(pb); + if (st->codec.codec_tag != MKTAG('R', 'V', '1', '0')) + goto fail1; + st->codec.width = get_be16(pb); + st->codec.height = get_be16(pb); + st->codec.frame_rate = get_be16(pb) * FRAME_RATE_BASE; + st->codec.codec_type = CODEC_TYPE_VIDEO; + get_be32(pb); + get_be16(pb); + get_be32(pb); + get_be16(pb); + /* modification of h263 codec version (!) */ + h263_hack_version = get_be32(pb); + switch(h263_hack_version) { + case 0x10000000: + case 0x10003000: + case 0x10003001: + st->codec.sub_id = h263_hack_version; + st->codec.codec_id = CODEC_ID_RV10; + break; + default: + /* not handled */ + st->codec.codec_id = CODEC_ID_NONE; + break; + } + } + /* skip codec info */ + size = url_ftell(pb) - codec_pos; + url_fskip(pb, codec_data_size - size); + break; + case MKTAG('D', 'A', 'T', 'A'): + goto header_end; + default: + /* unknown tag: skip it */ + url_fskip(pb, tag_size - 10); + break; + } + } + header_end: + rm->nb_packets = get_be32(pb); /* number of packets */ + if (!rm->nb_packets && (flags & 4)) + rm->nb_packets = 3600 * 25; + get_be32(pb); /* next data header */ + return 0; + + fail: + for(i=0;i<s->nb_streams;i++) { + av_free(s->streams[i]); + } + return -EIO; +} + +static int get_num(ByteIOContext *pb, int *len) +{ + int n, n1; + + n = get_be16(pb); + (*len)-=2; + if (n >= 0x4000) { + return n - 0x4000; + } else { + n1 = get_be16(pb); + (*len)-=2; + return (n << 16) | n1; + } +} + +static int rm_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + RMContext *rm = s->priv_data; + ByteIOContext *pb = &s->pb; + AVStream *st; + int len, num, timestamp, i, tmp, j; + UINT8 *ptr; + int flags; + + redo: + if (rm->nb_packets == 0) + return -EIO; + get_be16(pb); + len = get_be16(pb); + if (len < 12) + return -EIO; + num = get_be16(pb); + timestamp = get_be32(pb); + get_byte(pb); /* reserved */ + flags = get_byte(pb); /* flags */ + rm->nb_packets--; + len -= 12; + + st = NULL; + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + if (num == st->id) + break; + } + if (i == s->nb_streams) { + /* skip packet if unknown number */ + url_fskip(pb, len); + goto redo; + } + + if (st->codec.codec_type == CODEC_TYPE_VIDEO) { + int full_frame, h, pic_num; + + h= get_byte(pb); + if ((h & 0xc0) == 0xc0) { + int len2, pos; + full_frame = 1; + len2= get_num(pb, &len); + pos = get_num(pb, &len); + //printf("pos:%d\n",len); + len -= 2; + } else { + int seq, frame_size, pos; + full_frame = 0; + seq = get_byte(pb); + frame_size = get_num(pb, &len); + pos = get_num(pb, &len); + //printf("seq:%d, size:%d, pos:%d\n",seq,frame_size,pos); + len -= 3; + } + /* picture number */ + pic_num= get_byte(pb); + + //XXX/FIXME/HACK, demuxer should be fixed to send complete frames ... + if(st->codec.slice_offset==NULL) st->codec.slice_offset= (int*)malloc(sizeof(int)); + st->codec.slice_count= full_frame; + st->codec.slice_offset[0]= 0; + } + + av_new_packet(pkt, len); + pkt->stream_index = i; + get_buffer(pb, pkt->data, len); + + /* for AC3, needs to swap bytes */ + if (st->codec.codec_id == CODEC_ID_AC3) { + ptr = pkt->data; + for(j=0;j<len;j+=2) { + tmp = ptr[0]; + ptr[0] = ptr[1]; + ptr[1] = tmp; + ptr += 2; + } + } + return 0; +} + +static int rm_read_close(AVFormatContext *s) +{ + return 0; +} + +static int rm_probe(AVProbeData *p) +{ + /* check file header */ + if (p->buf_size <= 32) + return 0; + if (p->buf[0] == '.' && p->buf[1] == 'R' && + p->buf[2] == 'M' && p->buf[3] == 'F' && + p->buf[4] == 0 && p->buf[5] == 0) + return AVPROBE_SCORE_MAX; + else + return 0; +} + +static AVInputFormat rm_iformat = { + "rm", + "rm format", + sizeof(RMContext), + rm_probe, + rm_read_header, + rm_read_packet, + rm_read_close, +}; + +static AVOutputFormat rm_oformat = { + "rm", + "rm format", + "audio/x-pn-realaudio", + "rm,ra", + sizeof(RMContext), + CODEC_ID_AC3, + CODEC_ID_RV10, + rm_write_header, + rm_write_packet, + rm_write_trailer, +}; + +int rm_init(void) +{ + av_register_input_format(&rm_iformat); + av_register_output_format(&rm_oformat); + return 0; +} diff --git a/libavformat/rtp.c b/libavformat/rtp.c new file mode 100644 index 0000000000..f36da6d41d --- /dev/null +++ b/libavformat/rtp.c @@ -0,0 +1,687 @@ +/* + * RTP input/output format + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif +#include <netdb.h> + +//#define DEBUG + + +/* TODO: - add RTCP statistics reporting (should be optional). + + - add support for h263/mpeg4 packetized output : IDEA: send a + buffer to 'rtp_write_packet' contains all the packets for ONE + frame. Each packet should have a four byte header containing + the length in big endian format (same trick as + 'url_open_dyn_packet_buf') +*/ + +#define RTP_VERSION 2 + +#define RTP_MAX_SDES 256 /* maximum text length for SDES */ + +/* RTCP paquets use 0.5 % of the bandwidth */ +#define RTCP_TX_RATIO_NUM 5 +#define RTCP_TX_RATIO_DEN 1000 + +typedef enum { + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 +} rtcp_type_t; + +typedef enum { + RTCP_SDES_END = 0, + RTCP_SDES_CNAME = 1, + RTCP_SDES_NAME = 2, + RTCP_SDES_EMAIL = 3, + RTCP_SDES_PHONE = 4, + RTCP_SDES_LOC = 5, + RTCP_SDES_TOOL = 6, + RTCP_SDES_NOTE = 7, + RTCP_SDES_PRIV = 8, + RTCP_SDES_IMG = 9, + RTCP_SDES_DOOR = 10, + RTCP_SDES_SOURCE = 11 +} rtcp_sdes_type_t; + +enum RTPPayloadType { + RTP_PT_ULAW = 0, + RTP_PT_GSM = 3, + RTP_PT_G723 = 4, + RTP_PT_ALAW = 8, + RTP_PT_S16BE_STEREO = 10, + RTP_PT_S16BE_MONO = 11, + RTP_PT_MPEGAUDIO = 14, + RTP_PT_JPEG = 26, + RTP_PT_H261 = 31, + RTP_PT_MPEGVIDEO = 32, + RTP_PT_MPEG2TS = 33, + RTP_PT_H263 = 34, /* old H263 encapsulation */ + RTP_PT_PRIVATE = 96, +}; + +typedef struct RTPContext { + int payload_type; + UINT32 ssrc; + UINT16 seq; + UINT32 timestamp; + UINT32 base_timestamp; + UINT32 cur_timestamp; + int max_payload_size; + /* rtcp sender statistics receive */ + INT64 last_rtcp_ntp_time; + UINT32 last_rtcp_timestamp; + /* rtcp sender statistics */ + unsigned int packet_count; + unsigned int octet_count; + unsigned int last_octet_count; + int first_packet; + /* buffer for output */ + UINT8 buf[RTP_MAX_PACKET_LENGTH]; + UINT8 *buf_ptr; +} RTPContext; + +int rtp_get_codec_info(AVCodecContext *codec, int payload_type) +{ + switch(payload_type) { + case RTP_PT_ULAW: + codec->codec_id = CODEC_ID_PCM_MULAW; + codec->channels = 1; + codec->sample_rate = 8000; + break; + case RTP_PT_ALAW: + codec->codec_id = CODEC_ID_PCM_ALAW; + codec->channels = 1; + codec->sample_rate = 8000; + break; + case RTP_PT_S16BE_STEREO: + codec->codec_id = CODEC_ID_PCM_S16BE; + codec->channels = 2; + codec->sample_rate = 44100; + break; + case RTP_PT_S16BE_MONO: + codec->codec_id = CODEC_ID_PCM_S16BE; + codec->channels = 1; + codec->sample_rate = 44100; + break; + case RTP_PT_MPEGAUDIO: + codec->codec_id = CODEC_ID_MP2; + break; + case RTP_PT_JPEG: + codec->codec_id = CODEC_ID_MJPEG; + break; + case RTP_PT_MPEGVIDEO: + codec->codec_id = CODEC_ID_MPEG1VIDEO; + break; + default: + return -1; + } + return 0; +} + +/* return < 0 if unknown payload type */ +int rtp_get_payload_type(AVCodecContext *codec) +{ + int payload_type; + + /* compute the payload type */ + payload_type = -1; + switch(codec->codec_id) { + case CODEC_ID_PCM_MULAW: + payload_type = RTP_PT_ULAW; + break; + case CODEC_ID_PCM_ALAW: + payload_type = RTP_PT_ALAW; + break; + case CODEC_ID_PCM_S16BE: + if (codec->channels == 1) { + payload_type = RTP_PT_S16BE_MONO; + } else if (codec->channels == 2) { + payload_type = RTP_PT_S16BE_STEREO; + } + break; + case CODEC_ID_MP2: + case CODEC_ID_MP3LAME: + payload_type = RTP_PT_MPEGAUDIO; + break; + case CODEC_ID_MJPEG: + payload_type = RTP_PT_JPEG; + break; + case CODEC_ID_MPEG1VIDEO: + payload_type = RTP_PT_MPEGVIDEO; + break; + default: + break; + } + return payload_type; +} + +static inline UINT32 decode_be32(const UINT8 *p) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +static inline UINT32 decode_be64(const UINT8 *p) +{ + return ((UINT64)decode_be32(p) << 32) | decode_be32(p + 4); +} + +static int rtcp_parse_packet(AVFormatContext *s1, const unsigned char *buf, int len) +{ + RTPContext *s = s1->priv_data; + + if (buf[1] != 200) + return -1; + s->last_rtcp_ntp_time = decode_be64(buf + 8); + s->last_rtcp_timestamp = decode_be32(buf + 16); + return 0; +} + +/** + * Parse an RTP packet directly sent as raw data. Can only be used if + * 'raw' is given as input file + * @param s1 media file context + * @param pkt returned packet + * @param buf input buffer + * @param len buffer len + * @return zero if no error. + */ +int rtp_parse_packet(AVFormatContext *s1, AVPacket *pkt, + const unsigned char *buf, int len) +{ + RTPContext *s = s1->priv_data; + unsigned int ssrc, h; + int payload_type, seq, delta_timestamp; + AVStream *st; + UINT32 timestamp; + + if (len < 12) + return -1; + + if ((buf[0] & 0xc0) != (RTP_VERSION << 6)) + return -1; + if (buf[1] >= 200 && buf[1] <= 204) { + rtcp_parse_packet(s1, buf, len); + return -1; + } + payload_type = buf[1] & 0x7f; + seq = (buf[2] << 8) | buf[3]; + timestamp = decode_be32(buf + 4); + ssrc = decode_be32(buf + 8); + + if (s->payload_type < 0) { + s->payload_type = payload_type; + + if (payload_type == RTP_PT_MPEG2TS) { + /* XXX: special case : not a single codec but a whole stream */ + return -1; + } else { + st = av_new_stream(s1, 0); + if (!st) + return -1; + rtp_get_codec_info(&st->codec, payload_type); + } + } + + /* NOTE: we can handle only one payload type */ + if (s->payload_type != payload_type) + return -1; +#if defined(DEBUG) || 1 + if (seq != ((s->seq + 1) & 0xffff)) { + printf("RTP: PT=%02x: bad cseq %04x expected=%04x\n", + payload_type, seq, ((s->seq + 1) & 0xffff)); + } + s->seq = seq; +#endif + len -= 12; + buf += 12; + st = s1->streams[0]; + switch(st->codec.codec_id) { + case CODEC_ID_MP2: + /* better than nothing: skip mpeg audio RTP header */ + if (len <= 4) + return -1; + h = decode_be32(buf); + len -= 4; + buf += 4; + av_new_packet(pkt, len); + memcpy(pkt->data, buf, len); + break; + case CODEC_ID_MPEG1VIDEO: + /* better than nothing: skip mpeg audio RTP header */ + if (len <= 4) + return -1; + h = decode_be32(buf); + buf += 4; + len -= 4; + if (h & (1 << 26)) { + /* mpeg2 */ + if (len <= 4) + return -1; + buf += 4; + len -= 4; + } + av_new_packet(pkt, len); + memcpy(pkt->data, buf, len); + break; + default: + av_new_packet(pkt, len); + memcpy(pkt->data, buf, len); + break; + } + + if (s->last_rtcp_ntp_time != AV_NOPTS_VALUE) { + /* compute pts from timestamp with received ntp_time */ + delta_timestamp = timestamp - s->last_rtcp_timestamp; + /* XXX: do conversion, but not needed for mpeg at 90 KhZ */ + pkt->pts = s->last_rtcp_ntp_time + delta_timestamp; + } + return 0; +} + +static int rtp_read_header(AVFormatContext *s1, + AVFormatParameters *ap) +{ + RTPContext *s = s1->priv_data; + s->payload_type = -1; + s->last_rtcp_ntp_time = AV_NOPTS_VALUE; + return 0; +} + +static int rtp_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + char buf[RTP_MAX_PACKET_LENGTH]; + int ret; + + /* XXX: needs a better API for packet handling ? */ + for(;;) { + ret = url_read(url_fileno(&s1->pb), buf, sizeof(buf)); + if (ret < 0) + return AVERROR_IO; + if (rtp_parse_packet(s1, pkt, buf, ret) == 0) + break; + } + return 0; +} + +static int rtp_read_close(AVFormatContext *s1) +{ + // RTPContext *s = s1->priv_data; + return 0; +} + +static int rtp_probe(AVProbeData *p) +{ + if (strstart(p->filename, "rtp://", NULL)) + return AVPROBE_SCORE_MAX; + return 0; +} + +/* rtp output */ + +static int rtp_write_header(AVFormatContext *s1) +{ + RTPContext *s = s1->priv_data; + int payload_type, max_packet_size; + AVStream *st; + + if (s1->nb_streams != 1) + return -1; + st = s1->streams[0]; + + payload_type = rtp_get_payload_type(&st->codec); + if (payload_type < 0) + payload_type = RTP_PT_PRIVATE; /* private payload type */ + s->payload_type = payload_type; + + s->base_timestamp = random(); + s->timestamp = s->base_timestamp; + s->ssrc = random(); + s->first_packet = 1; + + max_packet_size = url_fget_max_packet_size(&s1->pb); + if (max_packet_size <= 12) + return AVERROR_IO; + s->max_payload_size = max_packet_size - 12; + + switch(st->codec.codec_id) { + case CODEC_ID_MP2: + case CODEC_ID_MP3LAME: + s->buf_ptr = s->buf + 4; + s->cur_timestamp = 0; + break; + case CODEC_ID_MPEG1VIDEO: + s->cur_timestamp = 0; + break; + default: + s->buf_ptr = s->buf; + break; + } + + return 0; +} + +/* send an rtcp sender report packet */ +static void rtcp_send_sr(AVFormatContext *s1, INT64 ntp_time) +{ + RTPContext *s = s1->priv_data; +#if defined(DEBUG) + printf("RTCP: %02x %Lx %x\n", s->payload_type, ntp_time, s->timestamp); +#endif + put_byte(&s1->pb, (RTP_VERSION << 6)); + put_byte(&s1->pb, 200); + put_be16(&s1->pb, 6); /* length in words - 1 */ + put_be32(&s1->pb, s->ssrc); + put_be64(&s1->pb, ntp_time); + put_be32(&s1->pb, s->timestamp); + put_be32(&s1->pb, s->packet_count); + put_be32(&s1->pb, s->octet_count); + put_flush_packet(&s1->pb); +} + +/* send an rtp packet. sequence number is incremented, but the caller + must update the timestamp itself */ +static void rtp_send_data(AVFormatContext *s1, UINT8 *buf1, int len) +{ + RTPContext *s = s1->priv_data; + +#ifdef DEBUG + printf("rtp_send_data size=%d\n", len); +#endif + + /* build the RTP header */ + put_byte(&s1->pb, (RTP_VERSION << 6)); + put_byte(&s1->pb, s->payload_type & 0x7f); + put_be16(&s1->pb, s->seq); + put_be32(&s1->pb, s->timestamp); + put_be32(&s1->pb, s->ssrc); + + put_buffer(&s1->pb, buf1, len); + put_flush_packet(&s1->pb); + + s->seq++; + s->octet_count += len; + s->packet_count++; +} + +/* send an integer number of samples and compute time stamp and fill + the rtp send buffer before sending. */ +static void rtp_send_samples(AVFormatContext *s1, + UINT8 *buf1, int size, int sample_size) +{ + RTPContext *s = s1->priv_data; + int len, max_packet_size, n; + + max_packet_size = (s->max_payload_size / sample_size) * sample_size; + /* not needed, but who nows */ + if ((size % sample_size) != 0) + av_abort(); + while (size > 0) { + len = (max_packet_size - (s->buf_ptr - s->buf)); + if (len > size) + len = size; + + /* copy data */ + memcpy(s->buf_ptr, buf1, len); + s->buf_ptr += len; + buf1 += len; + size -= len; + n = (s->buf_ptr - s->buf); + /* if buffer full, then send it */ + if (n >= max_packet_size) { + rtp_send_data(s1, s->buf, n); + s->buf_ptr = s->buf; + /* update timestamp */ + s->timestamp += n / sample_size; + } + } +} + +/* NOTE: we suppose that exactly one frame is given as argument here */ +/* XXX: test it */ +static void rtp_send_mpegaudio(AVFormatContext *s1, + UINT8 *buf1, int size) +{ + RTPContext *s = s1->priv_data; + AVStream *st = s1->streams[0]; + int len, count, max_packet_size; + + max_packet_size = s->max_payload_size; + + /* test if we must flush because not enough space */ + len = (s->buf_ptr - s->buf); + if ((len + size) > max_packet_size) { + if (len > 4) { + rtp_send_data(s1, s->buf, s->buf_ptr - s->buf); + s->buf_ptr = s->buf + 4; + /* 90 KHz time stamp */ + s->timestamp = s->base_timestamp + + (s->cur_timestamp * 90000LL) / st->codec.sample_rate; + } + } + + /* add the packet */ + if (size > max_packet_size) { + /* big packet: fragment */ + count = 0; + while (size > 0) { + len = max_packet_size - 4; + if (len > size) + len = size; + /* build fragmented packet */ + s->buf[0] = 0; + s->buf[1] = 0; + s->buf[2] = count >> 8; + s->buf[3] = count; + memcpy(s->buf + 4, buf1, len); + rtp_send_data(s1, s->buf, len + 4); + size -= len; + buf1 += len; + count += len; + } + } else { + if (s->buf_ptr == s->buf + 4) { + /* no fragmentation possible */ + s->buf[0] = 0; + s->buf[1] = 0; + s->buf[2] = 0; + s->buf[3] = 0; + } + memcpy(s->buf_ptr, buf1, size); + s->buf_ptr += size; + } + s->cur_timestamp += st->codec.frame_size; +} + +/* NOTE: a single frame must be passed with sequence header if + needed. XXX: use slices. */ +static void rtp_send_mpegvideo(AVFormatContext *s1, + UINT8 *buf1, int size) +{ + RTPContext *s = s1->priv_data; + AVStream *st = s1->streams[0]; + int len, h, max_packet_size; + UINT8 *q; + + max_packet_size = s->max_payload_size; + + while (size > 0) { + /* XXX: more correct headers */ + h = 0; + if (st->codec.sub_id == 2) + h |= 1 << 26; /* mpeg 2 indicator */ + q = s->buf; + *q++ = h >> 24; + *q++ = h >> 16; + *q++ = h >> 8; + *q++ = h; + + if (st->codec.sub_id == 2) { + h = 0; + *q++ = h >> 24; + *q++ = h >> 16; + *q++ = h >> 8; + *q++ = h; + } + + len = max_packet_size - (q - s->buf); + if (len > size) + len = size; + + memcpy(q, buf1, len); + q += len; + + /* 90 KHz time stamp */ + /* XXX: overflow */ + s->timestamp = s->base_timestamp + + (s->cur_timestamp * 90000LL * FRAME_RATE_BASE) / st->codec.frame_rate; + rtp_send_data(s1, s->buf, q - s->buf); + + buf1 += len; + size -= len; + } + s->cur_timestamp++; +} + +static void rtp_send_raw(AVFormatContext *s1, + UINT8 *buf1, int size) +{ + RTPContext *s = s1->priv_data; + AVStream *st = s1->streams[0]; + int len, max_packet_size; + + max_packet_size = s->max_payload_size; + + while (size > 0) { + len = max_packet_size; + if (len > size) + len = size; + + /* 90 KHz time stamp */ + /* XXX: overflow */ + s->timestamp = s->base_timestamp + + (s->cur_timestamp * 90000LL * FRAME_RATE_BASE) / st->codec.frame_rate; + rtp_send_data(s1, buf1, len); + + buf1 += len; + size -= len; + } + s->cur_timestamp++; +} + +/* write an RTP packet. 'buf1' must contain a single specific frame. */ +static int rtp_write_packet(AVFormatContext *s1, int stream_index, + UINT8 *buf1, int size, int force_pts) +{ + RTPContext *s = s1->priv_data; + AVStream *st = s1->streams[0]; + int rtcp_bytes; + INT64 ntp_time; + +#ifdef DEBUG + printf("%d: write len=%d\n", stream_index, size); +#endif + + /* XXX: mpeg pts hardcoded. RTCP send every 0.5 seconds */ + rtcp_bytes = ((s->octet_count - s->last_octet_count) * RTCP_TX_RATIO_NUM) / + RTCP_TX_RATIO_DEN; + if (s->first_packet || rtcp_bytes >= 28) { + /* compute NTP time */ + ntp_time = force_pts; // ((INT64)force_pts << 28) / 5625 + rtcp_send_sr(s1, ntp_time); + s->last_octet_count = s->octet_count; + s->first_packet = 0; + } + + switch(st->codec.codec_id) { + case CODEC_ID_PCM_MULAW: + case CODEC_ID_PCM_ALAW: + case CODEC_ID_PCM_U8: + case CODEC_ID_PCM_S8: + rtp_send_samples(s1, buf1, size, 1 * st->codec.channels); + break; + case CODEC_ID_PCM_U16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_S16LE: + rtp_send_samples(s1, buf1, size, 2 * st->codec.channels); + break; + case CODEC_ID_MP2: + case CODEC_ID_MP3LAME: + rtp_send_mpegaudio(s1, buf1, size); + break; + case CODEC_ID_MPEG1VIDEO: + rtp_send_mpegvideo(s1, buf1, size); + break; + default: + /* better than nothing : send the codec raw data */ + rtp_send_raw(s1, buf1, size); + break; + } + return 0; +} + +static int rtp_write_trailer(AVFormatContext *s1) +{ + // RTPContext *s = s1->priv_data; + return 0; +} + +AVInputFormat rtp_demux = { + "rtp", + "RTP input format", + sizeof(RTPContext), + rtp_probe, + rtp_read_header, + rtp_read_packet, + rtp_read_close, + .flags = AVFMT_NOHEADER, +}; + +AVOutputFormat rtp_mux = { + "rtp", + "RTP output format", + NULL, + NULL, + sizeof(RTPContext), + CODEC_ID_PCM_MULAW, + CODEC_ID_NONE, + rtp_write_header, + rtp_write_packet, + rtp_write_trailer, +}; + +int rtp_init(void) +{ + av_register_output_format(&rtp_mux); + av_register_input_format(&rtp_demux); + return 0; +} diff --git a/libavformat/rtp.h b/libavformat/rtp.h new file mode 100644 index 0000000000..0c0ae35ac0 --- /dev/null +++ b/libavformat/rtp.h @@ -0,0 +1,40 @@ +/* + * RTP definitions + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef RTP_H +#define RTP_H + +#define RTP_MIN_PACKET_LENGTH 12 +#define RTP_MAX_PACKET_LENGTH 1500 /* XXX: suppress this define */ + +int rtp_init(void); +int rtp_get_codec_info(AVCodecContext *codec, int payload_type); +int rtp_get_payload_type(AVCodecContext *codec); +int rtp_parse_packet(AVFormatContext *s1, AVPacket *pkt, + const unsigned char *buf, int len); + +extern AVOutputFormat rtp_mux; +extern AVInputFormat rtp_demux; + +int rtp_get_local_port(URLContext *h); +int rtp_set_remote_url(URLContext *h, const char *uri); +void rtp_get_file_handles(URLContext *h, int *prtp_fd, int *prtcp_fd); + +extern URLProtocol rtp_protocol; + +#endif /* RTP_H */ diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c new file mode 100644 index 0000000000..41823fc829 --- /dev/null +++ b/libavformat/rtpproto.c @@ -0,0 +1,300 @@ +/* + * RTP network protocol + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#include <unistd.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif +#include <netdb.h> +#include <fcntl.h> + +#define RTP_TX_BUF_SIZE (64 * 1024) +#define RTP_RX_BUF_SIZE (128 * 1024) + +typedef struct RTPContext { + URLContext *rtp_hd, *rtcp_hd; + int rtp_fd, rtcp_fd; +} RTPContext; + +/** + * If no filename is given to av_open_input_file because you want to + * get the local port first, then you must call this function to set + * the remote server address. + * + * @param s1 media file context + * @param uri of the remote server + * @return zero if no error. + */ +int rtp_set_remote_url(URLContext *h, const char *uri) +{ + RTPContext *s = h->priv_data; + char hostname[256]; + int port; + + char buf[1024]; + char path[1024]; + + url_split(NULL, 0, hostname, sizeof(hostname), &port, + path, sizeof(path), uri); + + snprintf(buf, sizeof(buf), "udp://%s:%d%s", hostname, port, path); + udp_set_remote_url(s->rtp_hd, buf); + + snprintf(buf, sizeof(buf), "udp://%s:%d%s", hostname, port + 1, path); + udp_set_remote_url(s->rtcp_hd, buf); + return 0; +} + + +/* add option to url of the form: + "http://host:port/path?option1=val1&option2=val2... */ +void url_add_option(char *buf, int buf_size, const char *fmt, ...) +{ + char buf1[1024]; + va_list ap; + + va_start(ap, fmt); + if (strchr(buf, '?')) + pstrcat(buf, buf_size, "&"); + else + pstrcat(buf, buf_size, "?"); + vsnprintf(buf1, sizeof(buf1), fmt, ap); + pstrcat(buf, buf_size, buf1); + va_end(ap); +} + +void build_udp_url(char *buf, int buf_size, + const char *hostname, int port, + int local_port, int multicast, int ttl) +{ + snprintf(buf, buf_size, "udp://%s:%d", hostname, port); + if (local_port >= 0) + url_add_option(buf, buf_size, "localport=%d", local_port); + if (multicast) + url_add_option(buf, buf_size, "multicast=1", multicast); + if (ttl >= 0) + url_add_option(buf, buf_size, "ttl=%d", ttl); +} + +/* + * url syntax: rtp://host:port[?option=val...] + * option: 'multicast=1' : enable multicast + * 'ttl=n' : set the ttl value (for multicast only) + * 'localport=n' : set the local port to n + * + */ +static int rtp_open(URLContext *h, const char *uri, int flags) +{ + RTPContext *s; + int port, is_output, is_multicast, ttl, local_port; + char hostname[256]; + char buf[1024]; + char path[1024]; + const char *p; + + is_output = (flags & URL_WRONLY); + + s = av_mallocz(sizeof(RTPContext)); + if (!s) + return -ENOMEM; + h->priv_data = s; + + url_split(NULL, 0, hostname, sizeof(hostname), &port, + path, sizeof(path), uri); + /* extract parameters */ + is_multicast = 0; + ttl = -1; + local_port = -1; + p = strchr(uri, '?'); + if (p) { + is_multicast = find_info_tag(buf, sizeof(buf), "multicast", p); + if (find_info_tag(buf, sizeof(buf), "ttl", p)) { + ttl = strtol(buf, NULL, 10); + } + if (find_info_tag(buf, sizeof(buf), "localport", p)) { + local_port = strtol(buf, NULL, 10); + } + } + + build_udp_url(buf, sizeof(buf), + hostname, port, local_port, is_multicast, ttl); + if (url_open(&s->rtp_hd, buf, flags) < 0) + goto fail; + local_port = udp_get_local_port(s->rtp_hd); + /* XXX: need to open another connexion if the port is not even */ + + /* well, should suppress localport in path */ + + build_udp_url(buf, sizeof(buf), + hostname, port + 1, local_port + 1, is_multicast, ttl); + if (url_open(&s->rtcp_hd, buf, flags) < 0) + goto fail; + + /* just to ease handle access. XXX: need to suppress direct handle + access */ + s->rtp_fd = udp_get_file_handle(s->rtp_hd); + s->rtcp_fd = udp_get_file_handle(s->rtcp_hd); + + h->max_packet_size = url_get_max_packet_size(s->rtp_hd); + h->is_streamed = 1; + return 0; + + fail: + if (s->rtp_hd) + url_close(s->rtp_hd); + if (s->rtcp_hd) + url_close(s->rtcp_hd); + av_free(s); + return -EIO; +} + +static int rtp_read(URLContext *h, UINT8 *buf, int size) +{ + RTPContext *s = h->priv_data; + struct sockaddr_in from; + int from_len, len, fd_max, n; + fd_set rfds; +#if 0 + for(;;) { + from_len = sizeof(from); + len = recvfrom (s->rtp_fd, buf, size, 0, + (struct sockaddr *)&from, &from_len); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return -EIO; + } + break; + } +#else + for(;;) { + /* build fdset to listen to RTP and RTCP packets */ + FD_ZERO(&rfds); + fd_max = s->rtp_fd; + FD_SET(s->rtp_fd, &rfds); + if (s->rtcp_fd > fd_max) + fd_max = s->rtcp_fd; + FD_SET(s->rtcp_fd, &rfds); + n = select(fd_max + 1, &rfds, NULL, NULL, NULL); + if (n > 0) { + /* first try RTCP */ + if (FD_ISSET(s->rtcp_fd, &rfds)) { + from_len = sizeof(from); + len = recvfrom (s->rtcp_fd, buf, size, 0, + (struct sockaddr *)&from, &from_len); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return -EIO; + } + break; + } + /* then RTP */ + if (FD_ISSET(s->rtp_fd, &rfds)) { + from_len = sizeof(from); + len = recvfrom (s->rtp_fd, buf, size, 0, + (struct sockaddr *)&from, &from_len); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return -EIO; + } + break; + } + } + } +#endif + return len; +} + +static int rtp_write(URLContext *h, UINT8 *buf, int size) +{ + RTPContext *s = h->priv_data; + int ret; + URLContext *hd; + + if (buf[1] >= 200 && buf[1] <= 204) { + /* RTCP payload type */ + hd = s->rtcp_hd; + } else { + /* RTP payload type */ + hd = s->rtp_hd; + } + + ret = url_write(hd, buf, size); +#if 0 + { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 10 * 1000000; + nanosleep(&ts, NULL); + } +#endif + return ret; +} + +static int rtp_close(URLContext *h) +{ + RTPContext *s = h->priv_data; + + url_close(s->rtp_hd); + url_close(s->rtcp_hd); + av_free(s); + return 0; +} + +/** + * Return the local port used by the RTP connexion + * @param s1 media file context + * @return the local port number + */ +int rtp_get_local_port(URLContext *h) +{ + RTPContext *s = h->priv_data; + return udp_get_local_port(s->rtp_hd); +} + +/** + * Return the rtp and rtcp file handles for select() usage to wait for several RTP + * streams at the same time. + * @param h media file context + */ +void rtp_get_file_handles(URLContext *h, int *prtp_fd, int *prtcp_fd) +{ + RTPContext *s = h->priv_data; + + *prtp_fd = s->rtp_fd; + *prtcp_fd = s->rtcp_fd; +} + +URLProtocol rtp_protocol = { + "rtp", + rtp_open, + rtp_read, + rtp_write, + NULL, /* seek */ + rtp_close, +}; diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c new file mode 100644 index 0000000000..c173cb89b7 --- /dev/null +++ b/libavformat/rtsp.c @@ -0,0 +1,1163 @@ +/* + * RTSP/SDP client + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +#include <sys/time.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <ctype.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif + +//#define DEBUG + +typedef struct RTSPState { + URLContext *rtsp_hd; /* RTSP TCP connexion handle */ + ByteIOContext rtsp_gb; + int seq; /* RTSP command sequence number */ + char session_id[512]; + enum RTSPProtocol protocol; + char last_reply[2048]; /* XXX: allocate ? */ +} RTSPState; + +typedef struct RTSPStream { + AVFormatContext *ic; + int interleaved_min, interleaved_max; /* interleave ids, if TCP transport */ + char control_url[1024]; /* url for this stream (from SDP) */ + + int sdp_port; /* port (from SDP content - not used in RTSP) */ + struct in_addr sdp_ip; /* IP address (from SDP content - not used in RTSP) */ + int sdp_ttl; /* IP TTL (from SDP content - not used in RTSP) */ + int sdp_payload_type; /* payload type - only used in SDP */ +} RTSPStream; + +/* suppress this hack */ +int rtsp_abort_req = 0; + +/* XXX: currently, the only way to change the protocols consists in + changing this variable */ +int rtsp_default_protocols = (1 << RTSP_PROTOCOL_RTP_TCP) | (1 << RTSP_PROTOCOL_RTP_UDP) | (1 << RTSP_PROTOCOL_RTP_UDP_MULTICAST); + +/* if non zero, then set a range for RTP ports */ +int rtsp_rtp_port_min = 0; +int rtsp_rtp_port_max = 0; + +FFRTSPCallback *ff_rtsp_callback = NULL; + +static int rtsp_probe(AVProbeData *p) +{ + if (strstart(p->filename, "rtsp:", NULL)) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int redir_isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); +} + +static void skip_spaces(const char **pp) +{ + const char *p; + p = *pp; + while (redir_isspace(*p)) + p++; + *pp = p; +} + +static void get_word_sep(char *buf, int buf_size, const char *sep, + const char **pp) +{ + const char *p; + char *q; + + p = *pp; + skip_spaces(&p); + q = buf; + while (!strchr(sep, *p) && *p != '\0') { + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + if (buf_size > 0) + *q = '\0'; + *pp = p; +} + +static void get_word(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + + p = *pp; + skip_spaces(&p); + q = buf; + while (!redir_isspace(*p) && *p != '\0') { + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + if (buf_size > 0) + *q = '\0'; + *pp = p; +} + +/* parse the rtpmap description: <codec_name>/<clock_rate>[/<other + params>] */ +static int sdp_parse_rtpmap(AVCodecContext *codec, const char *p) +{ + char buf[256]; + + /* codec name */ + get_word_sep(buf, sizeof(buf), "/", &p); + if (!strcmp(buf, "MP4V-ES")) { + codec->codec_id = CODEC_ID_MPEG4; + return 0; + } else { + return -1; + } +} + +/* return the length and optionnaly the data */ +static int hex_to_data(uint8_t *data, const char *p) +{ + int c, len, v; + + len = 0; + v = 1; + for(;;) { + skip_spaces(&p); + if (p == '\0') + break; + c = toupper((unsigned char)*p++); + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else + break; + v = (v << 4) | c; + if (v & 0x100) { + if (data) + data[len] = v; + len++; + v = 1; + } + } + return len; +} + +static void sdp_parse_fmtp(AVCodecContext *codec, const char *p) +{ + char attr[256]; + char value[4096]; + int len; + + /* loop on each attribute */ + for(;;) { + skip_spaces(&p); + if (*p == '\0') + break; + get_word_sep(attr, sizeof(attr), "=", &p); + if (*p == '=') + p++; + get_word_sep(value, sizeof(value), ";", &p); + if (*p == ';') + p++; + /* handle MPEG4 video */ + switch(codec->codec_id) { + case CODEC_ID_MPEG4: + if (!strcmp(attr, "config")) { + /* decode the hexa encoded parameter */ + len = hex_to_data(NULL, value); + codec->extradata = av_mallocz(len); + if (!codec->extradata) + goto fail; + codec->extradata_size = len; + hex_to_data(codec->extradata, value); + } + break; + default: + /* ignore data for other codecs */ + break; + } + fail: ; + // printf("'%s' = '%s'\n", attr, value); + } +} + +typedef struct SDPParseState { + /* SDP only */ + struct in_addr default_ip; + int default_ttl; +} SDPParseState; + +static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, + int letter, const char *buf) +{ + char buf1[64], st_type[64]; + const char *p; + int codec_type, payload_type, i; + AVStream *st; + RTSPStream *rtsp_st; + struct in_addr sdp_ip; + int ttl; + +#ifdef DEBUG + printf("sdp: %c='%s'\n", letter, buf); +#endif + + p = buf; + switch(letter) { + case 'c': + get_word(buf1, sizeof(buf1), &p); + if (strcmp(buf1, "IN") != 0) + return; + get_word(buf1, sizeof(buf1), &p); + if (strcmp(buf1, "IP4") != 0) + return; + get_word_sep(buf1, sizeof(buf1), "/", &p); + if (inet_aton(buf1, &sdp_ip) == 0) + return; + ttl = 16; + if (*p == '/') { + p++; + get_word_sep(buf1, sizeof(buf1), "/", &p); + ttl = atoi(buf1); + } + if (s->nb_streams == 0) { + s1->default_ip = sdp_ip; + s1->default_ttl = ttl; + } else { + st = s->streams[s->nb_streams - 1]; + rtsp_st = st->priv_data; + rtsp_st->sdp_ip = sdp_ip; + rtsp_st->sdp_ttl = ttl; + } + break; + case 's': + pstrcpy(s->title, sizeof(s->title), p); + break; + case 'i': + if (s->nb_streams == 0) { + pstrcpy(s->comment, sizeof(s->comment), p); + break; + } + break; + case 'm': + /* new stream */ + get_word(st_type, sizeof(st_type), &p); + if (!strcmp(st_type, "audio")) { + codec_type = CODEC_TYPE_AUDIO; + } else if (!strcmp(st_type, "video")) { + codec_type = CODEC_TYPE_VIDEO; + } else { + return; + } + rtsp_st = av_mallocz(sizeof(RTSPStream)); + if (!rtsp_st) + return; + st = av_new_stream(s, s->nb_streams); + if (!st) + return; + st->priv_data = rtsp_st; + + rtsp_st->sdp_ip = s1->default_ip; + rtsp_st->sdp_ttl = s1->default_ttl; + + st->codec.codec_type = codec_type; + + get_word(buf1, sizeof(buf1), &p); /* port */ + rtsp_st->sdp_port = atoi(buf1); + + get_word(buf1, sizeof(buf1), &p); /* protocol (ignored) */ + + /* XXX: handle list of formats */ + get_word(buf1, sizeof(buf1), &p); /* format list */ + rtsp_st->sdp_payload_type = atoi(buf1); + if (rtsp_st->sdp_payload_type < 96) { + /* if standard payload type, we can find the codec right now */ + rtp_get_codec_info(&st->codec, rtsp_st->sdp_payload_type); + } + + /* put a default control url */ + pstrcpy(rtsp_st->control_url, sizeof(rtsp_st->control_url), s->filename); + break; + case 'a': + if (strstart(p, "control:", &p) && s->nb_streams > 0) { + char proto[32]; + /* get the control url */ + st = s->streams[s->nb_streams - 1]; + rtsp_st = st->priv_data; + + /* XXX: may need to add full url resolution */ + url_split(proto, sizeof(proto), NULL, 0, NULL, NULL, 0, p); + if (proto[0] == '\0') { + /* relative control URL */ + pstrcat(rtsp_st->control_url, sizeof(rtsp_st->control_url), "/"); + pstrcat(rtsp_st->control_url, sizeof(rtsp_st->control_url), p); + } else { + pstrcpy(rtsp_st->control_url, sizeof(rtsp_st->control_url), p); + } + } else if (strstart(p, "rtpmap:", &p)) { + /* NOTE: rtpmap is only supported AFTER the 'm=' tag */ + get_word(buf1, sizeof(buf1), &p); + payload_type = atoi(buf1); + for(i = 0; i < s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st->sdp_payload_type == payload_type) { + sdp_parse_rtpmap(&st->codec, p); + } + } + } else if (strstart(p, "fmtp:", &p)) { + /* NOTE: fmtp is only supported AFTER the 'a=rtpmap:xxx' tag */ + get_word(buf1, sizeof(buf1), &p); + payload_type = atoi(buf1); + for(i = 0; i < s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st->sdp_payload_type == payload_type) { + sdp_parse_fmtp(&st->codec, p); + } + } + } + break; + } +} + +int sdp_parse(AVFormatContext *s, const char *content) +{ + const char *p; + int letter; + char buf[1024], *q; + SDPParseState sdp_parse_state, *s1 = &sdp_parse_state; + + memset(s1, 0, sizeof(SDPParseState)); + p = content; + for(;;) { + skip_spaces(&p); + letter = *p; + if (letter == '\0') + break; + p++; + if (*p != '=') + goto next_line; + p++; + /* get the content */ + q = buf; + while (*p != '\n' && *p != '\0') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + sdp_parse_line(s, s1, letter, buf); + next_line: + while (*p != '\n' && *p != '\0') + p++; + if (*p == '\n') + p++; + } + return 0; +} + +static void rtsp_parse_range(int *min_ptr, int *max_ptr, const char **pp) +{ + const char *p; + int v; + + p = *pp; + skip_spaces(&p); + v = strtol(p, (char **)&p, 10); + if (*p == '-') { + p++; + *min_ptr = v; + v = strtol(p, (char **)&p, 10); + *max_ptr = v; + } else { + *min_ptr = v; + *max_ptr = v; + } + *pp = p; +} + +/* XXX: only one transport specification is parsed */ +static void rtsp_parse_transport(RTSPHeader *reply, const char *p) +{ + char transport_protocol[16]; + char profile[16]; + char lower_transport[16]; + char parameter[16]; + RTSPTransportField *th; + char buf[256]; + + reply->nb_transports = 0; + + for(;;) { + skip_spaces(&p); + if (*p == '\0') + break; + + th = &reply->transports[reply->nb_transports]; + + get_word_sep(transport_protocol, sizeof(transport_protocol), + "/", &p); + if (*p == '/') + p++; + get_word_sep(profile, sizeof(profile), "/;,", &p); + lower_transport[0] = '\0'; + if (*p == '/') { + get_word_sep(lower_transport, sizeof(lower_transport), + ";,", &p); + } + if (!strcmp(lower_transport, "TCP")) + th->protocol = RTSP_PROTOCOL_RTP_TCP; + else + th->protocol = RTSP_PROTOCOL_RTP_UDP; + + if (*p == ';') + p++; + /* get each parameter */ + while (*p != '\0' && *p != ',') { + get_word_sep(parameter, sizeof(parameter), "=;,", &p); + if (!strcmp(parameter, "port")) { + if (*p == '=') { + p++; + rtsp_parse_range(&th->port_min, &th->port_max, &p); + } + } else if (!strcmp(parameter, "client_port")) { + if (*p == '=') { + p++; + rtsp_parse_range(&th->client_port_min, + &th->client_port_max, &p); + } + } else if (!strcmp(parameter, "server_port")) { + if (*p == '=') { + p++; + rtsp_parse_range(&th->server_port_min, + &th->server_port_max, &p); + } + } else if (!strcmp(parameter, "interleaved")) { + if (*p == '=') { + p++; + rtsp_parse_range(&th->interleaved_min, + &th->interleaved_max, &p); + } + } else if (!strcmp(parameter, "multicast")) { + if (th->protocol == RTSP_PROTOCOL_RTP_UDP) + th->protocol = RTSP_PROTOCOL_RTP_UDP_MULTICAST; + } else if (!strcmp(parameter, "ttl")) { + if (*p == '=') { + p++; + th->ttl = strtol(p, (char **)&p, 10); + } + } else if (!strcmp(parameter, "destination")) { + struct in_addr ipaddr; + + if (*p == '=') { + p++; + get_word_sep(buf, sizeof(buf), ";,", &p); + if (inet_aton(buf, &ipaddr)) + th->destination = ntohl(ipaddr.s_addr); + } + } + while (*p != ';' && *p != '\0' && *p != ',') + p++; + if (*p == ';') + p++; + } + if (*p == ',') + p++; + + reply->nb_transports++; + } +} + +void rtsp_parse_line(RTSPHeader *reply, const char *buf) +{ + const char *p; + + /* NOTE: we do case independent match for broken servers */ + p = buf; + if (stristart(p, "Session:", &p)) { + get_word_sep(reply->session_id, sizeof(reply->session_id), ";", &p); + } else if (stristart(p, "Content-Length:", &p)) { + reply->content_length = strtol(p, NULL, 10); + } else if (stristart(p, "Transport:", &p)) { + rtsp_parse_transport(reply, p); + } else if (stristart(p, "CSeq:", &p)) { + reply->seq = strtol(p, NULL, 10); + } +} + + +static void rtsp_send_cmd(AVFormatContext *s, + const char *cmd, RTSPHeader *reply, + unsigned char **content_ptr) +{ + RTSPState *rt = s->priv_data; + char buf[4096], buf1[1024], *q; + unsigned char ch; + const char *p; + int content_length, line_count; + unsigned char *content = NULL; + + memset(reply, 0, sizeof(RTSPHeader)); + + rt->seq++; + pstrcpy(buf, sizeof(buf), cmd); + snprintf(buf1, sizeof(buf1), "CSeq: %d\n", rt->seq); + pstrcat(buf, sizeof(buf), buf1); + if (rt->session_id[0] != '\0' && !strstr(cmd, "\nIf-Match:")) { + snprintf(buf1, sizeof(buf1), "Session: %s\n", rt->session_id); + pstrcat(buf, sizeof(buf), buf1); + } + pstrcat(buf, sizeof(buf), "\n"); +#ifdef DEBUG + printf("Sending:\n%s--\n", buf); +#endif + url_write(rt->rtsp_hd, buf, strlen(buf)); + + /* parse reply (XXX: use buffers) */ + line_count = 0; + rt->last_reply[0] = '\0'; + for(;;) { + q = buf; + for(;;) { + if (url_read(rt->rtsp_hd, &ch, 1) == 0) + break; + if (ch == '\n') + break; + if (ch != '\r') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = ch; + } + } + *q = '\0'; +#ifdef DEBUG + printf("line='%s'\n", buf); +#endif + /* test if last line */ + if (buf[0] == '\0') + break; + p = buf; + if (line_count == 0) { + /* get reply code */ + get_word(buf1, sizeof(buf1), &p); + get_word(buf1, sizeof(buf1), &p); + reply->status_code = atoi(buf1); + } else { + rtsp_parse_line(reply, p); + pstrcat(rt->last_reply, sizeof(rt->last_reply), p); + pstrcat(rt->last_reply, sizeof(rt->last_reply), "\n"); + } + line_count++; + } + + if (rt->session_id[0] == '\0' && reply->session_id[0] != '\0') + pstrcpy(rt->session_id, sizeof(rt->session_id), reply->session_id); + + content_length = reply->content_length; + if (content_length > 0) { + /* leave some room for a trailing '\0' (useful for simple parsing) */ + content = av_malloc(content_length + 1); + url_read(rt->rtsp_hd, content, content_length); + content[content_length] = '\0'; + } + if (content_ptr) + *content_ptr = content; +} + +/* useful for modules: set RTSP callback function */ + +void rtsp_set_callback(FFRTSPCallback *rtsp_cb) +{ + ff_rtsp_callback = rtsp_cb; +} + + +static int rtsp_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + RTSPState *rt = s->priv_data; + char host[1024], path[1024], tcpname[1024], cmd[2048]; + URLContext *rtsp_hd; + int port, i, ret, err; + RTSPHeader reply1, *reply = &reply1; + unsigned char *content = NULL; + AVStream *st; + RTSPStream *rtsp_st; + int protocol_mask; + + rtsp_abort_req = 0; + + /* extract hostname and port */ + url_split(NULL, 0, + host, sizeof(host), &port, path, sizeof(path), s->filename); + if (port < 0) + port = RTSP_DEFAULT_PORT; + + /* open the tcp connexion */ + snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); + if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) + return AVERROR_IO; + rt->rtsp_hd = rtsp_hd; + rt->seq = 0; + + /* describe the stream */ + snprintf(cmd, sizeof(cmd), + "DESCRIBE %s RTSP/1.0\n" + "Accept: application/sdp\n", + s->filename); + rtsp_send_cmd(s, cmd, reply, &content); + if (!content) { + err = AVERROR_INVALIDDATA; + goto fail; + } + if (reply->status_code != RTSP_STATUS_OK) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + /* now we got the SDP description, we parse it */ + ret = sdp_parse(s, (const char *)content); + av_freep(&content); + if (ret < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + protocol_mask = rtsp_default_protocols; + + /* for each stream, make the setup request */ + /* XXX: we assume the same server is used for the control of each + RTSP stream */ + for(i=0;i<s->nb_streams;i++) { + char transport[2048]; + AVInputFormat *fmt; + + st = s->streams[i]; + rtsp_st = st->priv_data; + + /* compute available transports */ + transport[0] = '\0'; + + /* RTP/UDP */ + if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP)) { + char buf[256]; + int j; + + /* first try in specified port range */ + if (rtsp_rtp_port_min != 0) { + for(j=rtsp_rtp_port_min;j<=rtsp_rtp_port_max;j++) { + snprintf(buf, sizeof(buf), "rtp://?localport=%d", j); + if (!av_open_input_file(&rtsp_st->ic, buf, + &rtp_demux, 0, NULL)) + goto rtp_opened; + } + } + + /* then try on any port */ + if (av_open_input_file(&rtsp_st->ic, "rtp://", + &rtp_demux, 0, NULL) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + rtp_opened: + port = rtp_get_local_port(url_fileno(&rtsp_st->ic->pb)); + if (transport[0] != '\0') + pstrcat(transport, sizeof(transport), ","); + snprintf(transport + strlen(transport), sizeof(transport) - strlen(transport) - 1, + "RTP/AVP/UDP;unicast;client_port=%d-%d", + port, port + 1); + } + + /* RTP/TCP */ + if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_TCP)) { + if (transport[0] != '\0') + pstrcat(transport, sizeof(transport), ","); + snprintf(transport + strlen(transport), sizeof(transport) - strlen(transport) - 1, + "RTP/AVP/TCP"); + } + + if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP_MULTICAST)) { + if (transport[0] != '\0') + pstrcat(transport, sizeof(transport), ","); + snprintf(transport + strlen(transport), + sizeof(transport) - strlen(transport) - 1, + "RTP/AVP/UDP;multicast"); + } + + snprintf(cmd, sizeof(cmd), + "SETUP %s RTSP/1.0\n" + "Transport: %s\n", + rtsp_st->control_url, transport); + rtsp_send_cmd(s, cmd, reply, NULL); + if (reply->status_code != RTSP_STATUS_OK || + reply->nb_transports != 1) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + /* XXX: same protocol for all streams is required */ + if (i > 0) { + if (reply->transports[0].protocol != rt->protocol) { + err = AVERROR_INVALIDDATA; + goto fail; + } + } else { + rt->protocol = reply->transports[0].protocol; + } + + /* close RTP connection if not choosen */ + if (reply->transports[0].protocol != RTSP_PROTOCOL_RTP_UDP && + (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP))) { + av_close_input_file(rtsp_st->ic); + rtsp_st->ic = NULL; + } + + switch(reply->transports[0].protocol) { + case RTSP_PROTOCOL_RTP_TCP: + fmt = &rtp_demux; + if (av_open_input_file(&rtsp_st->ic, "null", fmt, 0, NULL) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + rtsp_st->interleaved_min = reply->transports[0].interleaved_min; + rtsp_st->interleaved_max = reply->transports[0].interleaved_max; + break; + + case RTSP_PROTOCOL_RTP_UDP: + { + char url[1024]; + + /* XXX: also use address if specified */ + snprintf(url, sizeof(url), "rtp://%s:%d", + host, reply->transports[0].server_port_min); + if (rtp_set_remote_url(url_fileno(&rtsp_st->ic->pb), url) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + } + break; + case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + { + char url[1024]; + int ttl; + + fmt = &rtp_demux; + ttl = reply->transports[0].ttl; + if (!ttl) + ttl = 16; + snprintf(url, sizeof(url), "rtp://%s:%d?multicast=1&ttl=%d", + host, + reply->transports[0].server_port_min, + ttl); + if (av_open_input_file(&rtsp_st->ic, url, fmt, 0, NULL) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + } + break; + } + } + + /* use callback if available to extend setup */ + if (ff_rtsp_callback) { + if (ff_rtsp_callback(RTSP_ACTION_CLIENT_SETUP, rt->session_id, + NULL, 0, rt->last_reply) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + } + + /* start playing */ + snprintf(cmd, sizeof(cmd), + "PLAY %s RTSP/1.0\n", + s->filename); + rtsp_send_cmd(s, cmd, reply, NULL); + if (reply->status_code != RTSP_STATUS_OK) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + /* open TCP with bufferized input */ + if (rt->protocol == RTSP_PROTOCOL_RTP_TCP) { + if (url_fdopen(&rt->rtsp_gb, rt->rtsp_hd) < 0) { + err = AVERROR_NOMEM; + goto fail; + } + } + + return 0; + fail: + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st) { + if (rtsp_st->ic) + av_close_input_file(rtsp_st->ic); + } + av_free(rtsp_st); + } + av_freep(&content); + url_close(rt->rtsp_hd); + return err; +} + +static int tcp_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + RTSPState *rt = s->priv_data; + ByteIOContext *rtsp_gb = &rt->rtsp_gb; + int c, id, len, i, ret; + AVStream *st; + RTSPStream *rtsp_st; + char buf[RTP_MAX_PACKET_LENGTH]; + + redo: + for(;;) { + c = url_fgetc(rtsp_gb); + if (c == URL_EOF) + return AVERROR_IO; + if (c == '$') + break; + } + id = get_byte(rtsp_gb); + len = get_be16(rtsp_gb); + if (len > RTP_MAX_PACKET_LENGTH || len < 12) + goto redo; + /* get the data */ + get_buffer(rtsp_gb, buf, len); + + /* find the matching stream */ + for(i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (i >= rtsp_st->interleaved_min && + i <= rtsp_st->interleaved_max) + goto found; + } + goto redo; + found: + ret = rtp_parse_packet(rtsp_st->ic, pkt, buf, len); + if (ret < 0) + goto redo; + pkt->stream_index = i; + return ret; +} + +/* NOTE: output one packet at a time. May need to add a small fifo */ +static int udp_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + AVFormatContext *ic; + AVStream *st; + RTSPStream *rtsp_st; + fd_set rfds; + int fd1, fd2, fd_max, n, i, ret; + char buf[RTP_MAX_PACKET_LENGTH]; + struct timeval tv; + + for(;;) { + if (rtsp_abort_req) + return -EIO; + FD_ZERO(&rfds); + fd_max = -1; + for(i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + ic = rtsp_st->ic; + /* currently, we cannot probe RTCP handle because of blocking restrictions */ + rtp_get_file_handles(url_fileno(&ic->pb), &fd1, &fd2); + if (fd1 > fd_max) + fd_max = fd1; + FD_SET(fd1, &rfds); + } + /* XXX: also add proper API to abort */ + tv.tv_sec = 0; + tv.tv_usec = 500000; + n = select(fd_max + 1, &rfds, NULL, NULL, &tv); + if (n > 0) { + for(i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + ic = rtsp_st->ic; + rtp_get_file_handles(url_fileno(&ic->pb), &fd1, &fd2); + if (FD_ISSET(fd1, &rfds)) { + ret = url_read(url_fileno(&ic->pb), buf, sizeof(buf)); + if (ret >= 0 && + rtp_parse_packet(ic, pkt, buf, ret) == 0) { + pkt->stream_index = i; + return ret; + } + } + } + } + } +} + +static int rtsp_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + RTSPState *rt = s->priv_data; + int ret; + + switch(rt->protocol) { + default: + case RTSP_PROTOCOL_RTP_TCP: + ret = tcp_read_packet(s, pkt); + break; + case RTSP_PROTOCOL_RTP_UDP: + ret = udp_read_packet(s, pkt); + break; + } + return ret; +} + +static int rtsp_read_close(AVFormatContext *s) +{ + RTSPState *rt = s->priv_data; + AVStream *st; + RTSPStream *rtsp_st; + RTSPHeader reply1, *reply = &reply1; + int i; + char cmd[1024]; + + /* NOTE: it is valid to flush the buffer here */ + if (rt->protocol == RTSP_PROTOCOL_RTP_TCP) { + url_fclose(&rt->rtsp_gb); + } + + snprintf(cmd, sizeof(cmd), + "TEARDOWN %s RTSP/1.0\n", + s->filename); + rtsp_send_cmd(s, cmd, reply, NULL); + + if (ff_rtsp_callback) { + ff_rtsp_callback(RTSP_ACTION_CLIENT_TEARDOWN, rt->session_id, + NULL, 0, NULL); + } + + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st) { + if (rtsp_st->ic) + av_close_input_file(rtsp_st->ic); + } + av_free(rtsp_st); + } + url_close(rt->rtsp_hd); + return 0; +} + +static AVInputFormat rtsp_demux = { + "rtsp", + "RTSP input format", + sizeof(RTSPState), + rtsp_probe, + rtsp_read_header, + rtsp_read_packet, + rtsp_read_close, + .flags = AVFMT_NOFILE, +}; + +static int sdp_probe(AVProbeData *p1) +{ + const char *p; + + /* we look for a line beginning "c=IN IP4" */ + p = p1->buf; + while (*p != '\0') { + if (strstart(p, "c=IN IP4", NULL)) + return AVPROBE_SCORE_MAX / 2; + p = strchr(p, '\n'); + if (!p) + break; + p++; + if (*p == '\r') + p++; + } + return 0; +} + +#define SDP_MAX_SIZE 8192 + +static int sdp_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + AVStream *st; + RTSPStream *rtsp_st; + int size, i, err; + char *content; + char url[1024]; + + /* read the whole sdp file */ + /* XXX: better loading */ + content = av_malloc(SDP_MAX_SIZE); + size = get_buffer(&s->pb, content, SDP_MAX_SIZE - 1); + if (size <= 0) { + av_free(content); + return AVERROR_INVALIDDATA; + } + content[size] ='\0'; + + sdp_parse(s, content); + av_free(content); + + /* open each RTP stream */ + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + + snprintf(url, sizeof(url), "rtp://%s:%d?multicast=1&ttl=%d", + inet_ntoa(rtsp_st->sdp_ip), + rtsp_st->sdp_port, + rtsp_st->sdp_ttl); + if (av_open_input_file(&rtsp_st->ic, url, &rtp_demux, 0, NULL) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + } + return 0; + fail: + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st) { + if (rtsp_st->ic) + av_close_input_file(rtsp_st->ic); + } + av_free(rtsp_st); + } + return err; +} + +static int sdp_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + return udp_read_packet(s, pkt); +} + +static int sdp_read_close(AVFormatContext *s) +{ + AVStream *st; + RTSPStream *rtsp_st; + int i; + + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st) { + if (rtsp_st->ic) + av_close_input_file(rtsp_st->ic); + } + av_free(rtsp_st); + } + return 0; +} + + +static AVInputFormat sdp_demux = { + "sdp", + "SDP", + sizeof(RTSPState), + sdp_probe, + sdp_read_header, + sdp_read_packet, + sdp_read_close, +}; + + +/* dummy redirector format (used directly in av_open_input_file now) */ +static int redir_probe(AVProbeData *pd) +{ + const char *p; + p = pd->buf; + while (redir_isspace(*p)) + p++; + if (strstart(p, "http://", NULL) || + strstart(p, "rtsp://", NULL)) + return AVPROBE_SCORE_MAX; + return 0; +} + +/* called from utils.c */ +int redir_open(AVFormatContext **ic_ptr, ByteIOContext *f) +{ + char buf[4096], *q; + int c; + AVFormatContext *ic = NULL; + + /* parse each URL and try to open it */ + c = url_fgetc(f); + while (c != URL_EOF) { + /* skip spaces */ + for(;;) { + if (!redir_isspace(c)) + break; + c = url_fgetc(f); + } + if (c == URL_EOF) + break; + /* record url */ + q = buf; + for(;;) { + if (c == URL_EOF || redir_isspace(c)) + break; + if ((q - buf) < sizeof(buf) - 1) + *q++ = c; + c = url_fgetc(f); + } + *q = '\0'; + //printf("URL='%s'\n", buf); + /* try to open the media file */ + if (av_open_input_file(&ic, buf, NULL, 0, NULL) == 0) + break; + } + *ic_ptr = ic; + if (!ic) + return AVERROR_IO; + else + return 0; +} + +AVInputFormat redir_demux = { + "redir", + "Redirector format", + 0, + redir_probe, + NULL, + NULL, + NULL, +}; + +int rtsp_init(void) +{ + av_register_input_format(&rtsp_demux); + av_register_input_format(&redir_demux); + av_register_input_format(&sdp_demux); + return 0; +} diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h new file mode 100644 index 0000000000..3dd231571e --- /dev/null +++ b/libavformat/rtsp.h @@ -0,0 +1,86 @@ +/* + * RTSP definitions + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef RTSP_H +#define RTSP_H + +/* RTSP handling */ +enum RTSPStatusCode { +#define DEF(n, c, s) c = n, +#include "rtspcodes.h" +#undef DEF +}; + +enum RTSPProtocol { + RTSP_PROTOCOL_RTP_UDP = 0, + RTSP_PROTOCOL_RTP_TCP = 1, + RTSP_PROTOCOL_RTP_UDP_MULTICAST = 2, +}; + +#define RTSP_DEFAULT_PORT 554 +#define RTSP_MAX_TRANSPORTS 8 + +typedef struct RTSPTransportField { + int interleaved_min, interleaved_max; /* interleave ids, if TCP transport */ + int port_min, port_max; /* RTP ports */ + int client_port_min, client_port_max; /* RTP ports */ + int server_port_min, server_port_max; /* RTP ports */ + int ttl; /* ttl value */ + UINT32 destination; /* destination IP address */ + enum RTSPProtocol protocol; +} RTSPTransportField; + +typedef struct RTSPHeader { + int content_length; + enum RTSPStatusCode status_code; /* response code from server */ + int nb_transports; + RTSPTransportField transports[RTSP_MAX_TRANSPORTS]; + int seq; /* sequence number */ + char session_id[512]; +} RTSPHeader; + +/* the callback can be used to extend the connection setup/teardown step */ +enum RTSPCallbackAction { + RTSP_ACTION_SERVER_SETUP, + RTSP_ACTION_SERVER_TEARDOWN, + RTSP_ACTION_CLIENT_SETUP, + RTSP_ACTION_CLIENT_TEARDOWN, +}; + +typedef struct RTSPActionServerSetup { + UINT32 ipaddr; + char transport_option[512]; +} RTSPActionServerSetup; + +typedef int FFRTSPCallback(enum RTSPCallbackAction action, + const char *session_id, + char *buf, int buf_size, + void *arg); + +void rtsp_set_callback(FFRTSPCallback *rtsp_cb); + +int rtsp_init(void); +void rtsp_parse_line(RTSPHeader *reply, const char *buf); + +extern int rtsp_abort_req; +extern int rtsp_default_protocols; +extern int rtsp_rtp_port_min; +extern int rtsp_rtp_port_max; +extern FFRTSPCallback *ff_rtsp_callback; + +#endif /* RTSP_H */ diff --git a/libavformat/rtspcodes.h b/libavformat/rtspcodes.h new file mode 100644 index 0000000000..b967cb932a --- /dev/null +++ b/libavformat/rtspcodes.h @@ -0,0 +1,11 @@ +DEF(200, RTSP_STATUS_OK, "OK") +DEF(405, RTSP_STATUS_METHOD, "Method Not Allowed") +DEF(453, RTSP_STATUS_BANDWIDTH, "Not Enough Bandwidth") +DEF(454, RTSP_STATUS_SESSION, "Session Not Found") +DEF(455, RTSP_STATUS_STATE, "Method Not Valid in This State") +DEF(459, RTSP_STATUS_AGGREGATE, "Aggregate operation not allowed") +DEF(460, RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed") +DEF(461, RTSP_STATUS_TRANSPORT, "Unsupported transport") +DEF(500, RTSP_STATUS_INTERNAL, "Internal Server Error") +DEF(503, RTSP_STATUS_SERVICE, "Service Unavailable") +DEF(505, RTSP_STATUS_VERSION, "RTSP Version not supported") diff --git a/libavformat/strptime.c b/libavformat/strptime.c new file mode 100644 index 0000000000..7aece6a08e --- /dev/null +++ b/libavformat/strptime.c @@ -0,0 +1,1004 @@ +/* Convert a string representation of time to a time value. + Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* XXX This version of the implementation is not really complete. + Some of the fields cannot add information alone. But if seeing + some of them in the same format (such as year, week and weekday) + this is enough information for determining the date. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <time.h> + +#ifdef _LIBC +# include "../locale/localeinfo.h" +#endif + +#include "strptime.h" + +#ifndef __P +# if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +# define __P(args) args +# else +# define __P(args) () +# endif /* GCC. */ +#endif /* Not __P. */ + +#if ! HAVE_LOCALTIME_R && ! defined localtime_r +# ifdef _LIBC +# define localtime_r __localtime_r +# else +/* Approximate localtime_r as best we can in its absence. */ +# define localtime_r my_localtime_r +static struct tm *localtime_r __P ((const time_t *, struct tm *)); +static struct tm * +localtime_r (t, tp) + const time_t *t; + struct tm *tp; +{ + struct tm *l = localtime (t); + if (! l) + return 0; + *tp = *l; + return tp; +} +# endif /* ! _LIBC */ +#endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */ + + +#define match_char(ch1, ch2) if (ch1 != ch2) return NULL +#if defined __GNUC__ && __GNUC__ >= 2 +# define match_string(cs1, s2) \ + ({ size_t len = strlen (cs1); \ + int result = strncasecmp ((cs1), (s2), len) == 0; \ + if (result) (s2) += len; \ + result; }) +#else +/* Oh come on. Get a reasonable compiler. */ +# define match_string(cs1, s2) \ + (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1)) +#endif +/* We intentionally do not use isdigit() for testing because this will + lead to problems with the wide character version. */ +#define get_number(from, to, n) \ + do { \ + int __n = n; \ + val = 0; \ + while (*rp == ' ') \ + ++rp; \ + if (*rp < '0' || *rp > '9') \ + return NULL; \ + do { \ + val *= 10; \ + val += *rp++ - '0'; \ + } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \ + if (val < from || val > to) \ + return NULL; \ + } while (0) +#ifdef _NL_CURRENT +# define get_alt_number(from, to, n) \ + ({ \ + __label__ do_normal; \ + if (*decided != raw) \ + { \ + const char *alts = _NL_CURRENT (LC_TIME, ALT_DIGITS); \ + int __n = n; \ + int any = 0; \ + while (*rp == ' ') \ + ++rp; \ + val = 0; \ + do { \ + val *= 10; \ + while (*alts != '\0') \ + { \ + size_t len = strlen (alts); \ + if (strncasecmp (alts, rp, len) == 0) \ + break; \ + alts += len + 1; \ + ++val; \ + } \ + if (*alts == '\0') \ + { \ + if (*decided == not && ! any) \ + goto do_normal; \ + /* If we haven't read anything it's an error. */ \ + if (! any) \ + return NULL; \ + /* Correct the premature multiplication. */ \ + val /= 10; \ + break; \ + } \ + else \ + *decided = loc; \ + } while (--__n > 0 && val * 10 <= to); \ + if (val < from || val > to) \ + return NULL; \ + } \ + else \ + { \ + do_normal: \ + get_number (from, to, n); \ + } \ + 0; \ + }) +#else +# define get_alt_number(from, to, n) \ + /* We don't have the alternate representation. */ \ + get_number(from, to, n) +#endif +#define recursive(new_fmt) \ + (*(new_fmt) != '\0' \ + && (rp = strptime_internal (rp, (new_fmt), tm, decided, era_cnt)) != NULL) + + +#ifdef _LIBC +/* This is defined in locale/C-time.c in the GNU libc. */ +extern const struct locale_data _nl_C_LC_TIME; +extern const unsigned short int __mon_yday[2][13]; + +# define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string) +# define ab_weekday_name \ + (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string) +# define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string) +# define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string) +# define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string) +# define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string) +# define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string) +# define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string) +# define HERE_T_FMT_AMPM \ + (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string) +# define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string) + +# define strncasecmp(s1, s2, n) __strncasecmp (s1, s2, n) +#else +static char const weekday_name[][10] = + { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }; +static char const ab_weekday_name[][4] = + { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; +static char const month_name[][10] = + { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }; +static char const ab_month_name[][4] = + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; +# define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y" +# define HERE_D_FMT "%m/%d/%y" +# define HERE_AM_STR "AM" +# define HERE_PM_STR "PM" +# define HERE_T_FMT_AMPM "%I:%M:%S %p" +# define HERE_T_FMT "%H:%M:%S" + +const unsigned short int __mon_yday[2][13] = + { + /* Normal years. */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* Leap years. */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } + }; +#endif + +/* Status of lookup: do we use the locale data or the raw data? */ +enum locale_status { not, loc, raw }; + + +#ifndef __isleap +/* Nonzero if YEAR is a leap year (every 4 years, + except every 100th isn't, and every 400th is). */ +# define __isleap(year) \ + ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) +#endif + +/* Compute the day of the week. */ +static void +day_of_the_week (struct tm *tm) +{ + /* We know that January 1st 1970 was a Thursday (= 4). Compute the + the difference between this data in the one on TM and so determine + the weekday. */ + int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2); + int wday = (-473 + + (365 * (tm->tm_year - 70)) + + (corr_year / 4) + - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0) + + (((corr_year / 4) / 25) / 4) + + __mon_yday[0][tm->tm_mon] + + tm->tm_mday - 1); + tm->tm_wday = ((wday % 7) + 7) % 7; +} + +/* Compute the day of the year. */ +static void +day_of_the_year (struct tm *tm) +{ + tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon] + + (tm->tm_mday - 1)); +} + +static char * +#ifdef _LIBC +internal_function +#endif +strptime_internal __P ((const char *rp, const char *fmt, struct tm *tm, + enum locale_status *decided, int era_cnt)); + +static char * +#ifdef _LIBC +internal_function +#endif +strptime_internal (rp, fmt, tm, decided, era_cnt) + const char *rp; + const char *fmt; + struct tm *tm; + enum locale_status *decided; + int era_cnt; +{ + const char *rp_backup; + int cnt; + size_t val; + int have_I, is_pm; + int century, want_century; + int want_era; + int have_wday, want_xday; + int have_yday; + int have_mon, have_mday; +#ifdef _NL_CURRENT + size_t num_eras; +#endif + struct era_entry *era; + + have_I = is_pm = 0; + century = -1; + want_century = 0; + want_era = 0; + era = NULL; + + have_wday = want_xday = have_yday = have_mon = have_mday = 0; + + while (*fmt != '\0') + { + /* A white space in the format string matches 0 more or white + space in the input string. */ + if (isspace (*fmt)) + { + while (isspace (*rp)) + ++rp; + ++fmt; + continue; + } + + /* Any character but `%' must be matched by the same character + in the iput string. */ + if (*fmt != '%') + { + match_char (*fmt++, *rp++); + continue; + } + + ++fmt; +#ifndef _NL_CURRENT + /* We need this for handling the `E' modifier. */ + start_over: +#endif + + /* Make back up of current processing pointer. */ + rp_backup = rp; + + switch (*fmt++) + { + case '%': + /* Match the `%' character itself. */ + match_char ('%', *rp++); + break; + case 'a': + case 'A': + /* Match day of week. */ + for (cnt = 0; cnt < 7; ++cnt) + { +#ifdef _NL_CURRENT + if (*decided !=raw) + { + if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp)) + { + if (*decided == not + && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt), + weekday_name[cnt])) + *decided = loc; + break; + } + if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp)) + { + if (*decided == not + && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), + ab_weekday_name[cnt])) + *decided = loc; + break; + } + } +#endif + if (*decided != loc + && (match_string (weekday_name[cnt], rp) + || match_string (ab_weekday_name[cnt], rp))) + { + *decided = raw; + break; + } + } + if (cnt == 7) + /* Does not match a weekday name. */ + return NULL; + tm->tm_wday = cnt; + have_wday = 1; + break; + case 'b': + case 'B': + case 'h': + /* Match month name. */ + for (cnt = 0; cnt < 12; ++cnt) + { +#ifdef _NL_CURRENT + if (*decided !=raw) + { + if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp)) + { + if (*decided == not + && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt), + month_name[cnt])) + *decided = loc; + break; + } + if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp)) + { + if (*decided == not + && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), + ab_month_name[cnt])) + *decided = loc; + break; + } + } +#endif + if (match_string (month_name[cnt], rp) + || match_string (ab_month_name[cnt], rp)) + { + *decided = raw; + break; + } + } + if (cnt == 12) + /* Does not match a month name. */ + return NULL; + tm->tm_mon = cnt; + want_xday = 1; + break; + case 'c': + /* Match locale's date and time format. */ +#ifdef _NL_CURRENT + if (*decided != raw) + { + if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT))) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (*decided == not && + strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT)) + *decided = loc; + want_xday = 1; + break; + } + *decided = raw; + } +#endif + if (!recursive (HERE_D_T_FMT)) + return NULL; + want_xday = 1; + break; + case 'C': + /* Match century number. */ +#ifdef _NL_CURRENT + match_century: +#endif + get_number (0, 99, 2); + century = val; + want_xday = 1; + break; + case 'd': + case 'e': + /* Match day of month. */ + get_number (1, 31, 2); + tm->tm_mday = val; + have_mday = 1; + want_xday = 1; + break; + case 'F': + if (!recursive ("%Y-%m-%d")) + return NULL; + want_xday = 1; + break; + case 'x': +#ifdef _NL_CURRENT + if (*decided != raw) + { + if (!recursive (_NL_CURRENT (LC_TIME, D_FMT))) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (*decided == not + && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT)) + *decided = loc; + want_xday = 1; + break; + } + *decided = raw; + } +#endif + /* Fall through. */ + case 'D': + /* Match standard day format. */ + if (!recursive (HERE_D_FMT)) + return NULL; + want_xday = 1; + break; + case 'k': + case 'H': + /* Match hour in 24-hour clock. */ + get_number (0, 23, 2); + tm->tm_hour = val; + have_I = 0; + break; + case 'I': + /* Match hour in 12-hour clock. */ + get_number (1, 12, 2); + tm->tm_hour = val % 12; + have_I = 1; + break; + case 'j': + /* Match day number of year. */ + get_number (1, 366, 3); + tm->tm_yday = val - 1; + have_yday = 1; + break; + case 'm': + /* Match number of month. */ + get_number (1, 12, 2); + tm->tm_mon = val - 1; + have_mon = 1; + want_xday = 1; + break; + case 'M': + /* Match minute. */ + get_number (0, 59, 2); + tm->tm_min = val; + break; + case 'n': + case 't': + /* Match any white space. */ + while (isspace (*rp)) + ++rp; + break; + case 'p': + /* Match locale's equivalent of AM/PM. */ +#ifdef _NL_CURRENT + if (*decided != raw) + { + if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp)) + { + if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR)) + *decided = loc; + break; + } + if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp)) + { + if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR)) + *decided = loc; + is_pm = 1; + break; + } + *decided = raw; + } +#endif + if (!match_string (HERE_AM_STR, rp)) + if (match_string (HERE_PM_STR, rp)) + is_pm = 1; + else + return NULL; + break; + case 'r': +#ifdef _NL_CURRENT + if (*decided != raw) + { + if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM))) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (*decided == not && + strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM), + HERE_T_FMT_AMPM)) + *decided = loc; + break; + } + *decided = raw; + } +#endif + if (!recursive (HERE_T_FMT_AMPM)) + return NULL; + break; + case 'R': + if (!recursive ("%H:%M")) + return NULL; + break; + case 's': + { + /* The number of seconds may be very high so we cannot use + the `get_number' macro. Instead read the number + character for character and construct the result while + doing this. */ + time_t secs = 0; + if (*rp < '0' || *rp > '9') + /* We need at least one digit. */ + return NULL; + + do + { + secs *= 10; + secs += *rp++ - '0'; + } + while (*rp >= '0' && *rp <= '9'); + + if (localtime_r (&secs, tm) == NULL) + /* Error in function. */ + return NULL; + } + break; + case 'S': + get_number (0, 61, 2); + tm->tm_sec = val; + break; + case 'X': +#ifdef _NL_CURRENT + if (*decided != raw) + { + if (!recursive (_NL_CURRENT (LC_TIME, T_FMT))) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT)) + *decided = loc; + break; + } + *decided = raw; + } +#endif + /* Fall through. */ + case 'T': + if (!recursive (HERE_T_FMT)) + return NULL; + break; + case 'u': + get_number (1, 7, 1); + tm->tm_wday = val % 7; + have_wday = 1; + break; + case 'g': + get_number (0, 99, 2); + /* XXX This cannot determine any field in TM. */ + break; + case 'G': + if (*rp < '0' || *rp > '9') + return NULL; + /* XXX Ignore the number since we would need some more + information to compute a real date. */ + do + ++rp; + while (*rp >= '0' && *rp <= '9'); + break; + case 'U': + case 'V': + case 'W': + get_number (0, 53, 2); + /* XXX This cannot determine any field in TM without some + information. */ + break; + case 'w': + /* Match number of weekday. */ + get_number (0, 6, 1); + tm->tm_wday = val; + have_wday = 1; + break; + case 'y': +#ifdef _NL_CURRENT + match_year_in_century: +#endif + /* Match year within century. */ + get_number (0, 99, 2); + /* The "Year 2000: The Millennium Rollover" paper suggests that + values in the range 69-99 refer to the twentieth century. */ + tm->tm_year = val >= 69 ? val : val + 100; + /* Indicate that we want to use the century, if specified. */ + want_century = 1; + want_xday = 1; + break; + case 'Y': + /* Match year including century number. */ + get_number (0, 9999, 4); + tm->tm_year = val - 1900; + want_century = 0; + want_xday = 1; + break; + case 'Z': + /* XXX How to handle this? */ + break; + case 'E': +#ifdef _NL_CURRENT + switch (*fmt++) + { + case 'c': + /* Match locale's alternate date and time format. */ + if (*decided != raw) + { + const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT); + + if (*fmt == '\0') + fmt = _NL_CURRENT (LC_TIME, D_T_FMT); + + if (!recursive (fmt)) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (strcmp (fmt, HERE_D_T_FMT)) + *decided = loc; + want_xday = 1; + break; + } + *decided = raw; + } + /* The C locale has no era information, so use the + normal representation. */ + if (!recursive (HERE_D_T_FMT)) + return NULL; + want_xday = 1; + break; + case 'C': + if (*decided != raw) + { + if (era_cnt >= 0) + { + era = _nl_select_era_entry (era_cnt); + if (match_string (era->era_name, rp)) + { + *decided = loc; + break; + } + else + return NULL; + } + else + { + num_eras = _NL_CURRENT_WORD (LC_TIME, + _NL_TIME_ERA_NUM_ENTRIES); + for (era_cnt = 0; era_cnt < (int) num_eras; + ++era_cnt, rp = rp_backup) + { + era = _nl_select_era_entry (era_cnt); + if (match_string (era->era_name, rp)) + { + *decided = loc; + break; + } + } + if (era_cnt == (int) num_eras) + { + era_cnt = -1; + if (*decided == loc) + return NULL; + } + else + break; + } + + *decided = raw; + } + /* The C locale has no era information, so use the + normal representation. */ + goto match_century; + case 'y': + if (*decided == raw) + goto match_year_in_century; + + get_number(0, 9999, 4); + tm->tm_year = val; + want_era = 1; + want_xday = 1; + break; + case 'Y': + if (*decided != raw) + { + num_eras = _NL_CURRENT_WORD (LC_TIME, + _NL_TIME_ERA_NUM_ENTRIES); + for (era_cnt = 0; era_cnt < (int) num_eras; + ++era_cnt, rp = rp_backup) + { + era = _nl_select_era_entry (era_cnt); + if (recursive (era->era_format)) + break; + } + if (era_cnt == (int) num_eras) + { + era_cnt = -1; + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + *decided = loc; + era_cnt = -1; + break; + } + + *decided = raw; + } + get_number (0, 9999, 4); + tm->tm_year = val - 1900; + want_century = 0; + want_xday = 1; + break; + case 'x': + if (*decided != raw) + { + const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT); + + if (*fmt == '\0') + fmt = _NL_CURRENT (LC_TIME, D_FMT); + + if (!recursive (fmt)) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (strcmp (fmt, HERE_D_FMT)) + *decided = loc; + break; + } + *decided = raw; + } + if (!recursive (HERE_D_FMT)) + return NULL; + break; + case 'X': + if (*decided != raw) + { + const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT); + + if (*fmt == '\0') + fmt = _NL_CURRENT (LC_TIME, T_FMT); + + if (!recursive (fmt)) + { + if (*decided == loc) + return NULL; + else + rp = rp_backup; + } + else + { + if (strcmp (fmt, HERE_T_FMT)) + *decided = loc; + break; + } + *decided = raw; + } + if (!recursive (HERE_T_FMT)) + return NULL; + break; + default: + return NULL; + } + break; +#else + /* We have no information about the era format. Just use + the normal format. */ + if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y' + && *fmt != 'x' && *fmt != 'X') + /* This is an illegal format. */ + return NULL; + + goto start_over; +#endif + case 'O': + switch (*fmt++) + { + case 'd': + case 'e': + /* Match day of month using alternate numeric symbols. */ + get_alt_number (1, 31, 2); + tm->tm_mday = val; + have_mday = 1; + want_xday = 1; + break; + case 'H': + /* Match hour in 24-hour clock using alternate numeric + symbols. */ + get_alt_number (0, 23, 2); + tm->tm_hour = val; + have_I = 0; + break; + case 'I': + /* Match hour in 12-hour clock using alternate numeric + symbols. */ + get_alt_number (1, 12, 2); + tm->tm_hour = val - 1; + have_I = 1; + break; + case 'm': + /* Match month using alternate numeric symbols. */ + get_alt_number (1, 12, 2); + tm->tm_mon = val - 1; + have_mon = 1; + want_xday = 1; + break; + case 'M': + /* Match minutes using alternate numeric symbols. */ + get_alt_number (0, 59, 2); + tm->tm_min = val; + break; + case 'S': + /* Match seconds using alternate numeric symbols. */ + get_alt_number (0, 61, 2); + tm->tm_sec = val; + break; + case 'U': + case 'V': + case 'W': + get_alt_number (0, 53, 2); + /* XXX This cannot determine any field in TM without + further information. */ + break; + case 'w': + /* Match number of weekday using alternate numeric symbols. */ + get_alt_number (0, 6, 1); + tm->tm_wday = val; + have_wday = 1; + break; + case 'y': + /* Match year within century using alternate numeric symbols. */ + get_alt_number (0, 99, 2); + tm->tm_year = val >= 69 ? val : val + 100; + want_xday = 1; + break; + default: + return NULL; + } + break; + default: + return NULL; + } + } + + if (have_I && is_pm) + tm->tm_hour += 12; + + if (century != -1) + { + if (want_century) + tm->tm_year = tm->tm_year % 100 + (century - 19) * 100; + else + /* Only the century, but not the year. Strange, but so be it. */ + tm->tm_year = (century - 19) * 100; + } + +#ifdef _NL_CURRENT + if (era_cnt != -1) + { + era = _nl_select_era_entry(era_cnt); + if (want_era) + tm->tm_year = (era->start_date[0] + + ((tm->tm_year - era->offset) + * era->absolute_direction)); + else + /* Era start year assumed. */ + tm->tm_year = era->start_date[0]; + } + else +#endif + if (want_era) + return NULL; + + if (want_xday && !have_wday) + { + if ( !(have_mon && have_mday) && have_yday) + { + /* We don't have tm_mon and/or tm_mday, compute them. */ + int t_mon = 0; + while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday) + t_mon++; + if (!have_mon) + tm->tm_mon = t_mon - 1; + if (!have_mday) + tm->tm_mday = + (tm->tm_yday + - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1); + } + day_of_the_week (tm); + } + if (want_xday && !have_yday) + day_of_the_year (tm); + + return (char *) rp; +} + + +char * +strptime (buf, format, tm) + const char *buf; + const char *format; + struct tm *tm; +{ + enum locale_status decided; + +#ifdef _NL_CURRENT + decided = not; +#else + decided = raw; +#endif + return strptime_internal (buf, format, tm, &decided, -1); +} diff --git a/libavformat/strptime.h b/libavformat/strptime.h new file mode 100644 index 0000000000..a1106fda40 --- /dev/null +++ b/libavformat/strptime.h @@ -0,0 +1,32 @@ +/* strptime.h + * + * $Id$ + * + * Ethereal - Network traffic analyzer + * By Gerald Combs <gerald@ethereal.com> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __STRPTIME_H__ +#define __STRPTIME_H__ + +/* + * Version of "strptime()", for the benefit of OSes that don't have it. + */ +extern char *strptime(const char *, const char *, struct tm *); + +#endif diff --git a/libavformat/swf.c b/libavformat/swf.c new file mode 100644 index 0000000000..14f8707f96 --- /dev/null +++ b/libavformat/swf.c @@ -0,0 +1,571 @@ +/* + * Flash Compatible Streaming Format + * Copyright (c) 2000 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" + +/* should have a generic way to indicate probable size */ +#define DUMMY_FILE_SIZE (100 * 1024 * 1024) +#define DUMMY_DURATION 600 /* in seconds */ + +#define TAG_END 0 +#define TAG_SHOWFRAME 1 +#define TAG_DEFINESHAPE 2 +#define TAG_FREECHARACTER 3 +#define TAG_PLACEOBJECT 4 +#define TAG_REMOVEOBJECT 5 +#define TAG_STREAMHEAD 18 +#define TAG_STREAMBLOCK 19 +#define TAG_JPEG2 21 + +#define TAG_LONG 0x100 + +/* flags for shape definition */ +#define FLAG_MOVETO 0x01 +#define FLAG_SETFILL0 0x02 +#define FLAG_SETFILL1 0x04 + +/* character id used */ +#define BITMAP_ID 0 +#define SHAPE_ID 1 + +typedef struct { + offset_t duration_pos; + offset_t tag_pos; + int tag; +} SWFContext; + +static void put_swf_tag(AVFormatContext *s, int tag) +{ + SWFContext *swf = s->priv_data; + ByteIOContext *pb = &s->pb; + + swf->tag_pos = url_ftell(pb); + swf->tag = tag; + /* reserve some room for the tag */ + if (tag & TAG_LONG) { + put_le16(pb, 0); + put_le32(pb, 0); + } else { + put_le16(pb, 0); + } +} + +static void put_swf_end_tag(AVFormatContext *s) +{ + SWFContext *swf = s->priv_data; + ByteIOContext *pb = &s->pb; + offset_t pos; + int tag_len, tag; + + pos = url_ftell(pb); + tag_len = pos - swf->tag_pos - 2; + tag = swf->tag; + url_fseek(pb, swf->tag_pos, SEEK_SET); + if (tag & TAG_LONG) { + tag &= ~TAG_LONG; + put_le16(pb, (tag << 6) | 0x3f); + put_le32(pb, tag_len - 4); + } else { + assert(tag_len < 0x3f); + put_le16(pb, (tag << 6) | tag_len); + } + url_fseek(pb, pos, SEEK_SET); +} + +static inline void max_nbits(int *nbits_ptr, int val) +{ + int n; + + if (val == 0) + return; + val = abs(val); + n = 1; + while (val != 0) { + n++; + val >>= 1; + } + if (n > *nbits_ptr) + *nbits_ptr = n; +} + +static void put_swf_rect(ByteIOContext *pb, + int xmin, int xmax, int ymin, int ymax) +{ + PutBitContext p; + UINT8 buf[256]; + int nbits, mask; + + init_put_bits(&p, buf, sizeof(buf), NULL, NULL); + + nbits = 0; + max_nbits(&nbits, xmin); + max_nbits(&nbits, xmax); + max_nbits(&nbits, ymin); + max_nbits(&nbits, ymax); + mask = (1 << nbits) - 1; + + /* rectangle info */ + put_bits(&p, 5, nbits); + put_bits(&p, nbits, xmin & mask); + put_bits(&p, nbits, xmax & mask); + put_bits(&p, nbits, ymin & mask); + put_bits(&p, nbits, ymax & mask); + + flush_put_bits(&p); + put_buffer(pb, buf, pbBufPtr(&p) - p.buf); +} + +static void put_swf_line_edge(PutBitContext *pb, int dx, int dy) +{ + int nbits, mask; + + put_bits(pb, 1, 1); /* edge */ + put_bits(pb, 1, 1); /* line select */ + nbits = 2; + max_nbits(&nbits, dx); + max_nbits(&nbits, dy); + + mask = (1 << nbits) - 1; + put_bits(pb, 4, nbits - 2); /* 16 bits precision */ + if (dx == 0) { + put_bits(pb, 1, 0); + put_bits(pb, 1, 1); + put_bits(pb, nbits, dy & mask); + } else if (dy == 0) { + put_bits(pb, 1, 0); + put_bits(pb, 1, 0); + put_bits(pb, nbits, dx & mask); + } else { + put_bits(pb, 1, 1); + put_bits(pb, nbits, dx & mask); + put_bits(pb, nbits, dy & mask); + } +} + +#define FRAC_BITS 16 + +/* put matrix (not size optimized */ +static void put_swf_matrix(ByteIOContext *pb, + int a, int b, int c, int d, int tx, int ty) +{ + PutBitContext p; + UINT8 buf[256]; + + init_put_bits(&p, buf, sizeof(buf), NULL, NULL); + + put_bits(&p, 1, 1); /* a, d present */ + put_bits(&p, 5, 20); /* nb bits */ + put_bits(&p, 20, a); + put_bits(&p, 20, d); + + put_bits(&p, 1, 1); /* b, c present */ + put_bits(&p, 5, 20); /* nb bits */ + put_bits(&p, 20, c); + put_bits(&p, 20, b); + + put_bits(&p, 5, 20); /* nb bits */ + put_bits(&p, 20, tx); + put_bits(&p, 20, ty); + + flush_put_bits(&p); + put_buffer(pb, buf, pbBufPtr(&p) - p.buf); +} + +/* XXX: handle audio only */ +static int swf_write_header(AVFormatContext *s) +{ + SWFContext *swf; + ByteIOContext *pb = &s->pb; + AVCodecContext *enc, *audio_enc, *video_enc; + PutBitContext p; + UINT8 buf1[256]; + int i, width, height, rate; + + swf = av_malloc(sizeof(SWFContext)); + if (!swf) + return -1; + s->priv_data = swf; + + video_enc = NULL; + audio_enc = NULL; + for(i=0;i<s->nb_streams;i++) { + enc = &s->streams[i]->codec; + if (enc->codec_type == CODEC_TYPE_AUDIO) + audio_enc = enc; + else + video_enc = enc; + } + + if (!video_enc) { + /* currenty, cannot work correctly if audio only */ + width = 320; + height = 200; + rate = 10 * FRAME_RATE_BASE; + } else { + width = video_enc->width; + height = video_enc->height; + rate = video_enc->frame_rate; + } + + put_tag(pb, "FWS"); + put_byte(pb, 4); /* version (should use 4 for mpeg audio support) */ + put_le32(pb, DUMMY_FILE_SIZE); /* dummy size + (will be patched if not streamed) */ + + put_swf_rect(pb, 0, width, 0, height); + put_le16(pb, (rate * 256) / FRAME_RATE_BASE); /* frame rate */ + swf->duration_pos = url_ftell(pb); + put_le16(pb, (UINT16)(DUMMY_DURATION * (INT64)rate / FRAME_RATE_BASE)); /* frame count */ + + /* define a shape with the jpeg inside */ + + put_swf_tag(s, TAG_DEFINESHAPE); + + put_le16(pb, SHAPE_ID); /* ID of shape */ + /* bounding rectangle */ + put_swf_rect(pb, 0, width, 0, height); + /* style info */ + put_byte(pb, 1); /* one fill style */ + put_byte(pb, 0x41); /* clipped bitmap fill */ + put_le16(pb, BITMAP_ID); /* bitmap ID */ + /* position of the bitmap */ + put_swf_matrix(pb, (int)(1.0 * (1 << FRAC_BITS)), 0, + 0, (int)(1.0 * (1 << FRAC_BITS)), 0, 0); + put_byte(pb, 0); /* no line style */ + + /* shape drawing */ + init_put_bits(&p, buf1, sizeof(buf1), NULL, NULL); + put_bits(&p, 4, 1); /* one fill bit */ + put_bits(&p, 4, 0); /* zero line bit */ + + put_bits(&p, 1, 0); /* not an edge */ + put_bits(&p, 5, FLAG_MOVETO | FLAG_SETFILL0); + put_bits(&p, 5, 1); /* nbits */ + put_bits(&p, 1, 0); /* X */ + put_bits(&p, 1, 0); /* Y */ + put_bits(&p, 1, 1); /* set fill style 1 */ + + /* draw the rectangle ! */ + put_swf_line_edge(&p, width, 0); + put_swf_line_edge(&p, 0, height); + put_swf_line_edge(&p, -width, 0); + put_swf_line_edge(&p, 0, -height); + + /* end of shape */ + put_bits(&p, 1, 0); /* not an edge */ + put_bits(&p, 5, 0); + + flush_put_bits(&p); + put_buffer(pb, buf1, pbBufPtr(&p) - p.buf); + + put_swf_end_tag(s); + + + if (audio_enc) { + int v; + + /* start sound */ + + v = 0; + switch(audio_enc->sample_rate) { + case 11025: + v |= 1 << 2; + break; + case 22050: + v |= 2 << 2; + break; + case 44100: + v |= 3 << 2; + break; + default: + /* not supported */ + av_free(swf); + return -1; + } + if (audio_enc->channels == 2) + v |= 1; + v |= 0x20; /* mp3 compressed */ + v |= 0x02; /* 16 bits */ + + put_swf_tag(s, TAG_STREAMHEAD); + put_byte(&s->pb, 0); + put_byte(&s->pb, v); + put_le16(&s->pb, (audio_enc->sample_rate * FRAME_RATE_BASE) / rate); /* avg samples per frame */ + + + put_swf_end_tag(s); + } + + put_flush_packet(&s->pb); + return 0; +} + +static int swf_write_video(AVFormatContext *s, + AVCodecContext *enc, UINT8 *buf, int size) +{ + ByteIOContext *pb = &s->pb; + static int tag_id = 0; + + if (enc->frame_number > 1) { + /* remove the shape */ + put_swf_tag(s, TAG_REMOVEOBJECT); + put_le16(pb, SHAPE_ID); /* shape ID */ + put_le16(pb, 1); /* depth */ + put_swf_end_tag(s); + + /* free the bitmap */ + put_swf_tag(s, TAG_FREECHARACTER); + put_le16(pb, BITMAP_ID); + put_swf_end_tag(s); + } + + put_swf_tag(s, TAG_JPEG2 | TAG_LONG); + + put_le16(pb, tag_id); /* ID of the image */ + + /* a dummy jpeg header seems to be required */ + put_byte(pb, 0xff); + put_byte(pb, 0xd8); + put_byte(pb, 0xff); + put_byte(pb, 0xd9); + /* write the jpeg image */ + put_buffer(pb, buf, size); + + put_swf_end_tag(s); + + /* draw the shape */ + + put_swf_tag(s, TAG_PLACEOBJECT); + put_le16(pb, SHAPE_ID); /* shape ID */ + put_le16(pb, 1); /* depth */ + put_swf_matrix(pb, 1 << FRAC_BITS, 0, 0, 1 << FRAC_BITS, 0, 0); + put_swf_end_tag(s); + + /* output the frame */ + put_swf_tag(s, TAG_SHOWFRAME); + put_swf_end_tag(s); + + put_flush_packet(&s->pb); + return 0; +} + +static int swf_write_audio(AVFormatContext *s, UINT8 *buf, int size) +{ + ByteIOContext *pb = &s->pb; + + put_swf_tag(s, TAG_STREAMBLOCK | TAG_LONG); + + put_buffer(pb, buf, size); + + put_swf_end_tag(s); + put_flush_packet(&s->pb); + return 0; +} + +static int swf_write_packet(AVFormatContext *s, int stream_index, + UINT8 *buf, int size, int force_pts) +{ + AVCodecContext *codec = &s->streams[stream_index]->codec; + if (codec->codec_type == CODEC_TYPE_AUDIO) + return swf_write_audio(s, buf, size); + else + return swf_write_video(s, codec, buf, size); +} + +static int swf_write_trailer(AVFormatContext *s) +{ + SWFContext *swf = s->priv_data; + ByteIOContext *pb = &s->pb; + AVCodecContext *enc, *video_enc; + int file_size, i; + + video_enc = NULL; + for(i=0;i<s->nb_streams;i++) { + enc = &s->streams[i]->codec; + if (enc->codec_type == CODEC_TYPE_VIDEO) + video_enc = enc; + } + + put_swf_tag(s, TAG_END); + put_swf_end_tag(s); + + put_flush_packet(&s->pb); + + /* patch file size and number of frames if not streamed */ + if (!url_is_streamed(&s->pb) && video_enc) { + file_size = url_ftell(pb); + url_fseek(pb, 4, SEEK_SET); + put_le32(pb, file_size); + url_fseek(pb, swf->duration_pos, SEEK_SET); + put_le16(pb, video_enc->frame_number); + } + return 0; +} + +/***********************************/ +/* just to extract MP3 from swf */ + +static int get_swf_tag(ByteIOContext *pb, int *len_ptr) +{ + int tag, len; + + if (url_feof(pb)) + return -1; + + tag = get_le16(pb); + len = tag & 0x3f; + tag = tag >> 6; + if (len == 0x3f) { + len = get_le32(pb); + } + *len_ptr = len; + return tag; +} + + +static int swf_probe(AVProbeData *p) +{ + /* check file header */ + if (p->buf_size <= 16) + return 0; + if (p->buf[0] == 'F' && p->buf[1] == 'W' && + p->buf[2] == 'S') + return AVPROBE_SCORE_MAX; + else + return 0; +} + +static int swf_read_header(AVFormatContext *s, AVFormatParameters *ap) +{ + ByteIOContext *pb = &s->pb; + int nbits, len, frame_rate, tag, v; + AVStream *st; + + if ((get_be32(pb) & 0xffffff00) != MKBETAG('F', 'W', 'S', 0)) + return -EIO; + get_le32(pb); + /* skip rectangle size */ + nbits = get_byte(pb) >> 3; + len = (4 * nbits - 3 + 7) / 8; + url_fskip(pb, len); + frame_rate = get_le16(pb); + get_le16(pb); /* frame count */ + + for(;;) { + tag = get_swf_tag(pb, &len); + if (tag < 0) { + fprintf(stderr, "No streaming found in SWF\n"); + return -EIO; + } + if (tag == TAG_STREAMHEAD) { + /* streaming found */ + get_byte(pb); + v = get_byte(pb); + get_le16(pb); + /* if mp3 streaming found, OK */ + if ((v & 0x20) != 0) { + st = av_mallocz(sizeof(AVStream)); + if (!st) + return -ENOMEM; + if (v & 0x01) + st->codec.channels = 2; + else + st->codec.channels = 1; + s->nb_streams = 1; + s->streams[0] = st; + + switch((v>> 2) & 0x03) { + case 1: + st->codec.sample_rate = 11025; + break; + case 2: + st->codec.sample_rate = 22050; + break; + case 3: + st->codec.sample_rate = 44100; + break; + default: + av_free(st); + return -EIO; + } + st->codec.codec_type = CODEC_TYPE_AUDIO; + st->codec.codec_id = CODEC_ID_MP2; + break; + } + } else { + url_fskip(pb, len); + } + } + + return 0; +} + +static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + ByteIOContext *pb = &s->pb; + int tag, len; + + for(;;) { + tag = get_swf_tag(pb, &len); + if (tag < 0) + return -EIO; + if (tag == TAG_STREAMBLOCK) { + av_new_packet(pkt, len); + get_buffer(pb, pkt->data, pkt->size); + break; + } else { + url_fskip(pb, len); + } + } + return 0; +} + +static int swf_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat swf_iformat = { + "swf", + "Flash format", + 0, + swf_probe, + swf_read_header, + swf_read_packet, + swf_read_close, +}; + +static AVOutputFormat swf_oformat = { + "swf", + "Flash format", + "application/x-shockwave-flash", + "swf", + sizeof(SWFContext), + CODEC_ID_MP2, + CODEC_ID_MJPEG, + swf_write_header, + swf_write_packet, + swf_write_trailer, +}; + +int swf_init(void) +{ + av_register_input_format(&swf_iformat); + av_register_output_format(&swf_oformat); + return 0; +} diff --git a/libavformat/tcp.c b/libavformat/tcp.c new file mode 100644 index 0000000000..61d8665525 --- /dev/null +++ b/libavformat/tcp.c @@ -0,0 +1,176 @@ +/* + * TCP protocol + * Copyright (c) 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <unistd.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif +#include <netdb.h> + +typedef struct TCPContext { + int fd; +} TCPContext; + +/* resolve host with also IP address parsing */ +int resolve_host(struct in_addr *sin_addr, const char *hostname) +{ + struct hostent *hp; + + if ((inet_aton(hostname, sin_addr)) == 0) { + hp = gethostbyname(hostname); + if (!hp) + return -1; + memcpy (sin_addr, hp->h_addr, sizeof(struct in_addr)); + } + return 0; +} + +/* return non zero if error */ +static int tcp_open(URLContext *h, const char *uri, int flags) +{ + struct sockaddr_in dest_addr; + char hostname[1024], *q; + int port, fd = -1; + TCPContext *s; + const char *p; + + s = av_malloc(sizeof(TCPContext)); + if (!s) + return -ENOMEM; + h->priv_data = s; + p = uri; + if (!strstart(p, "tcp://", &p)) + goto fail; + q = hostname; + while (*p != ':' && *p != '/' && *p != '\0') { + if ((q - hostname) < sizeof(hostname) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (*p != ':') + goto fail; + p++; + port = strtoul(p, (char **)&p, 10); + if (port <= 0 || port >= 65536) + goto fail; + + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(port); + if (resolve_host(&dest_addr.sin_addr, hostname) < 0) + goto fail; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + goto fail; + + if (connect(fd, (struct sockaddr *)&dest_addr, + sizeof(dest_addr)) < 0) + goto fail; + + s->fd = fd; + return 0; + + fail: + if (fd >= 0) + close(fd); + av_free(s); + return -EIO; +} + +static int tcp_read(URLContext *h, UINT8 *buf, int size) +{ + TCPContext *s = h->priv_data; + int size1, len; + + size1 = size; + while (size > 0) { +#ifdef CONFIG_BEOS_NETSERVER + len = recv (s->fd, buf, size, 0); +#else + len = read (s->fd, buf, size); +#endif + if (len < 0) { + if (errno != EINTR && errno != EAGAIN) +#ifdef __BEOS__ + return errno; +#else + return -errno; +#endif + else + continue; + } else if (len == 0) { + break; + } + size -= len; + buf += len; + } + return size1 - size; +} + +static int tcp_write(URLContext *h, UINT8 *buf, int size) +{ + TCPContext *s = h->priv_data; + int ret, size1; + + size1 = size; + while (size > 0) { +#ifdef CONFIG_BEOS_NETSERVER + ret = send (s->fd, buf, size, 0); +#else + ret = write (s->fd, buf, size); +#endif + if (ret < 0 && errno != EINTR && errno != EAGAIN) +#ifdef __BEOS__ + return errno; +#else + return -errno; +#endif + size -= ret; + buf += ret; + } + return size1 - size; +} + +static int tcp_close(URLContext *h) +{ + TCPContext *s = h->priv_data; +#ifdef CONFIG_BEOS_NETSERVER + closesocket(s->fd); +#else + close(s->fd); +#endif + av_free(s); + return 0; +} + +URLProtocol tcp_protocol = { + "tcp", + tcp_open, + tcp_read, + tcp_write, + NULL, /* seek */ + tcp_close, +}; diff --git a/libavformat/udp.c b/libavformat/udp.c new file mode 100644 index 0000000000..3d159ef4f8 --- /dev/null +++ b/libavformat/udp.c @@ -0,0 +1,272 @@ +/* + * UDP prototype streaming system + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#ifndef __BEOS__ +# include <arpa/inet.h> +#else +# include "barpainet.h" +#endif +#include <netdb.h> + +typedef struct { + int udp_fd; + int ttl; + int is_multicast; + int local_port; + struct ip_mreq mreq; + struct sockaddr_in dest_addr; +} UDPContext; + +#define UDP_TX_BUF_SIZE 32768 + +/** + * If no filename is given to av_open_input_file because you want to + * get the local port first, then you must call this function to set + * the remote server address. + * + * url syntax: udp://host:port[?option=val...] + * option: 'multicast=1' : enable multicast + * 'ttl=n' : set the ttl value (for multicast only) + * 'localport=n' : set the local port + * + * @param s1 media file context + * @param uri of the remote server + * @return zero if no error. + */ +int udp_set_remote_url(URLContext *h, const char *uri) +{ + UDPContext *s = h->priv_data; + char hostname[256]; + int port; + + url_split(NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); + + /* set the destination address */ + if (resolve_host(&s->dest_addr.sin_addr, hostname) < 0) + return -EIO; + s->dest_addr.sin_family = AF_INET; + s->dest_addr.sin_port = htons(port); + return 0; +} + +/** + * Return the local port used by the UDP connexion + * @param s1 media file context + * @return the local port number + */ +int udp_get_local_port(URLContext *h) +{ + UDPContext *s = h->priv_data; + return s->local_port; +} + +/** + * Return the udp file handle for select() usage to wait for several RTP + * streams at the same time. + * @param h media file context + */ +int udp_get_file_handle(URLContext *h) +{ + UDPContext *s = h->priv_data; + return s->udp_fd; +} + +/* put it in UDP context */ +/* return non zero if error */ +static int udp_open(URLContext *h, const char *uri, int flags) +{ + struct sockaddr_in my_addr, my_addr1; + char hostname[1024]; + int port, udp_fd = -1, tmp; + UDPContext *s = NULL; + int is_output, len; + const char *p; + char buf[256]; + + h->is_streamed = 1; + + is_output = (flags & URL_WRONLY); + + s = av_malloc(sizeof(UDPContext)); + if (!s) + return -ENOMEM; + + h->priv_data = s; + s->ttl = 16; + s->is_multicast = 0; + p = strchr(uri, '?'); + if (p) { + s->is_multicast = find_info_tag(buf, sizeof(buf), "multicast", p); + if (find_info_tag(buf, sizeof(buf), "ttl", p)) { + s->ttl = strtol(buf, NULL, 10); + } + if (find_info_tag(buf, sizeof(buf), "localport", p)) { + s->local_port = strtol(buf, NULL, 10); + } + } + + /* fill the dest addr */ + url_split(NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); + + /* XXX: fix url_split */ + if (hostname[0] == '\0' || hostname[0] == '?') { + /* only accepts null hostname if input */ + if (s->is_multicast || (flags & URL_WRONLY)) + goto fail; + } else { + udp_set_remote_url(h, uri); + } + + udp_fd = socket(PF_INET, SOCK_DGRAM, 0); + if (udp_fd < 0) + goto fail; + + my_addr.sin_family = AF_INET; + my_addr.sin_addr.s_addr = htonl (INADDR_ANY); + if (s->is_multicast && !(h->flags & URL_WRONLY)) { + /* special case: the bind must be done on the multicast address port */ + my_addr.sin_port = s->dest_addr.sin_port; + } else { + my_addr.sin_port = htons(s->local_port); + } + + /* the bind is needed to give a port to the socket now */ + if (bind(udp_fd,(struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) + goto fail; + + len = sizeof(my_addr1); + getsockname(udp_fd, (struct sockaddr *)&my_addr1, &len); + s->local_port = ntohs(my_addr1.sin_port); + +#ifndef CONFIG_BEOS_NETSERVER + if (s->is_multicast) { + if (h->flags & URL_WRONLY) { + /* output */ + if (setsockopt(udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, + &s->ttl, sizeof(s->ttl)) < 0) { + perror("IP_MULTICAST_TTL"); + goto fail; + } + } else { + /* input */ + memset(&s->mreq, 0, sizeof(s->mreq)); + s->mreq.imr_multiaddr = s->dest_addr.sin_addr; + s->mreq.imr_interface.s_addr = htonl (INADDR_ANY); + if (setsockopt(udp_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &s->mreq, sizeof(s->mreq)) < 0) { + perror("rtp: IP_ADD_MEMBERSHIP"); + goto fail; + } + } + } +#endif + + if (is_output) { + /* limit the tx buf size to limit latency */ + tmp = UDP_TX_BUF_SIZE; + if (setsockopt(udp_fd, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)) < 0) { + perror("setsockopt sndbuf"); + goto fail; + } + } + + s->udp_fd = udp_fd; + h->max_packet_size = 1472; /* XXX: probe it ? */ + return 0; + fail: + if (udp_fd >= 0) +#ifdef CONFIG_BEOS_NETSERVER + closesocket(udp_fd); +#else + close(udp_fd); +#endif + av_free(s); + return -EIO; +} + +static int udp_read(URLContext *h, UINT8 *buf, int size) +{ + UDPContext *s = h->priv_data; + struct sockaddr_in from; + int from_len, len; + + for(;;) { + from_len = sizeof(from); + len = recvfrom (s->udp_fd, buf, size, 0, + (struct sockaddr *)&from, &from_len); + if (len < 0) { + if (errno != EAGAIN && errno != EINTR) + return -EIO; + } else { + break; + } + } + return len; +} + +static int udp_write(URLContext *h, UINT8 *buf, int size) +{ + UDPContext *s = h->priv_data; + int ret; + + for(;;) { + ret = sendto (s->udp_fd, buf, size, 0, + (struct sockaddr *) &s->dest_addr, + sizeof (s->dest_addr)); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -EIO; + } else { + break; + } + } + return size; +} + +static int udp_close(URLContext *h) +{ + UDPContext *s = h->priv_data; + +#ifndef CONFIG_BEOS_NETSERVER + if (s->is_multicast && !(h->flags & URL_WRONLY)) { + if (setsockopt(s->udp_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &s->mreq, sizeof(s->mreq)) < 0) { + perror("IP_DROP_MEMBERSHIP"); + } + } + close(s->udp_fd); +#else + closesocket(s->udp_fd); +#endif + av_free(s); + return 0; +} + +URLProtocol udp_protocol = { + "udp", + udp_open, + udp_read, + udp_write, + NULL, /* seek */ + udp_close, +}; diff --git a/libavformat/utils.c b/libavformat/utils.c new file mode 100644 index 0000000000..5a9aa082f4 --- /dev/null +++ b/libavformat/utils.c @@ -0,0 +1,1280 @@ +/* + * Various utilities for ffmpeg system + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include <ctype.h> +#ifndef CONFIG_WIN32 +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> +#else +#define strcasecmp _stricmp +#include <sys/types.h> +#include <sys/timeb.h> +#endif +#include <time.h> + +#ifndef HAVE_STRPTIME +#include "strptime.h" +#endif + +AVInputFormat *first_iformat; +AVOutputFormat *first_oformat; + +void av_register_input_format(AVInputFormat *format) +{ + AVInputFormat **p; + p = &first_iformat; + while (*p != NULL) p = &(*p)->next; + *p = format; + format->next = NULL; +} + +void av_register_output_format(AVOutputFormat *format) +{ + AVOutputFormat **p; + p = &first_oformat; + while (*p != NULL) p = &(*p)->next; + *p = format; + format->next = NULL; +} + +int match_ext(const char *filename, const char *extensions) +{ + const char *ext, *p; + char ext1[32], *q; + + ext = strrchr(filename, '.'); + if (ext) { + ext++; + p = extensions; + for(;;) { + q = ext1; + while (*p != '\0' && *p != ',') + *q++ = *p++; + *q = '\0'; + if (!strcasecmp(ext1, ext)) + return 1; + if (*p == '\0') + break; + p++; + } + } + return 0; +} + +AVOutputFormat *guess_format(const char *short_name, const char *filename, + const char *mime_type) +{ + AVOutputFormat *fmt, *fmt_found; + int score_max, score; + + /* find the proper file type */ + fmt_found = NULL; + score_max = 0; + fmt = first_oformat; + while (fmt != NULL) { + score = 0; + if (fmt->name && short_name && !strcmp(fmt->name, short_name)) + score += 100; + if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type)) + score += 10; + if (filename && fmt->extensions && + match_ext(filename, fmt->extensions)) { + score += 5; + } + if (score > score_max) { + score_max = score; + fmt_found = fmt; + } + fmt = fmt->next; + } + return fmt_found; +} + +AVOutputFormat *guess_stream_format(const char *short_name, const char *filename, + const char *mime_type) +{ + AVOutputFormat *fmt = guess_format(short_name, filename, mime_type); + + if (fmt) { + AVOutputFormat *stream_fmt; + char stream_format_name[64]; + + snprintf(stream_format_name, sizeof(stream_format_name), "%s_stream", fmt->name); + stream_fmt = guess_format(stream_format_name, NULL, NULL); + + if (stream_fmt) + fmt = stream_fmt; + } + + return fmt; +} + +AVInputFormat *av_find_input_format(const char *short_name) +{ + AVInputFormat *fmt; + for(fmt = first_iformat; fmt != NULL; fmt = fmt->next) { + if (!strcmp(fmt->name, short_name)) + return fmt; + } + return NULL; +} + +/* memory handling */ + +/** + * Allocate the payload of a packet and intialized its fields to default values. + * + * @param pkt packet + * @param size wanted payload size + * @return 0 if OK. AVERROR_xxx otherwise. + */ +int av_new_packet(AVPacket *pkt, int size) +{ + int i; + pkt->data = av_malloc(size + FF_INPUT_BUFFER_PADDING_SIZE); + if (!pkt->data) + return AVERROR_NOMEM; + pkt->size = size; + /* sane state */ + pkt->pts = AV_NOPTS_VALUE; + pkt->stream_index = 0; + pkt->flags = 0; + + for(i=0; i<FF_INPUT_BUFFER_PADDING_SIZE; i++) + pkt->data[size+i]= 0; + + return 0; +} + +/** + * Free a packet + * + * @param pkt packet to free + */ +void av_free_packet(AVPacket *pkt) +{ + av_freep(&pkt->data); + /* fail safe */ + pkt->size = 0; +} + +/* fifo handling */ + +int fifo_init(FifoBuffer *f, int size) +{ + f->buffer = av_malloc(size); + if (!f->buffer) + return -1; + f->end = f->buffer + size; + f->wptr = f->rptr = f->buffer; + return 0; +} + +void fifo_free(FifoBuffer *f) +{ + av_free(f->buffer); +} + +int fifo_size(FifoBuffer *f, UINT8 *rptr) +{ + int size; + + if (f->wptr >= rptr) { + size = f->wptr - rptr; + } else { + size = (f->end - rptr) + (f->wptr - f->buffer); + } + return size; +} + +/* get data from the fifo (return -1 if not enough data) */ +int fifo_read(FifoBuffer *f, UINT8 *buf, int buf_size, UINT8 **rptr_ptr) +{ + UINT8 *rptr = *rptr_ptr; + int size, len; + + if (f->wptr >= rptr) { + size = f->wptr - rptr; + } else { + size = (f->end - rptr) + (f->wptr - f->buffer); + } + + if (size < buf_size) + return -1; + while (buf_size > 0) { + len = f->end - rptr; + if (len > buf_size) + len = buf_size; + memcpy(buf, rptr, len); + buf += len; + rptr += len; + if (rptr >= f->end) + rptr = f->buffer; + buf_size -= len; + } + *rptr_ptr = rptr; + return 0; +} + +void fifo_write(FifoBuffer *f, UINT8 *buf, int size, UINT8 **wptr_ptr) +{ + int len; + UINT8 *wptr; + wptr = *wptr_ptr; + while (size > 0) { + len = f->end - wptr; + if (len > size) + len = size; + memcpy(wptr, buf, len); + wptr += len; + if (wptr >= f->end) + wptr = f->buffer; + buf += len; + size -= len; + } + *wptr_ptr = wptr; +} + +int filename_number_test(const char *filename) +{ + char buf[1024]; + return get_frame_filename(buf, sizeof(buf), filename, 1); +} + +/* guess file format */ +AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened) +{ + AVInputFormat *fmt1, *fmt; + int score, score_max; + + fmt = NULL; + score_max = 0; + for(fmt1 = first_iformat; fmt1 != NULL; fmt1 = fmt1->next) { + if (!is_opened && !(fmt1->flags & AVFMT_NOFILE)) + continue; + score = 0; + if (fmt1->read_probe) { + score = fmt1->read_probe(pd); + } else if (fmt1->extensions) { + if (match_ext(pd->filename, fmt1->extensions)) { + score = 50; + } + } + if (score > score_max) { + score_max = score; + fmt = fmt1; + } + } + return fmt; +} + +/************************************************************/ +/* input media file */ + +#define PROBE_BUF_SIZE 2048 + +/** + * Open a media file as input. The codec are not opened. Only the file + * header (if present) is read. + * + * @param ic_ptr the opened media file handle is put here + * @param filename filename to open. + * @param fmt if non NULL, force the file format to use + * @param buf_size optional buffer size (zero if default is OK) + * @param ap additionnal parameters needed when opening the file (NULL if default) + * @return 0 if OK. AVERROR_xxx otherwise. + */ +int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, + AVInputFormat *fmt, + int buf_size, + AVFormatParameters *ap) +{ + AVFormatContext *ic = NULL; + int err; + char buf[PROBE_BUF_SIZE]; + AVProbeData probe_data, *pd = &probe_data; + + ic = av_mallocz(sizeof(AVFormatContext)); + if (!ic) { + err = AVERROR_NOMEM; + goto fail; + } + pstrcpy(ic->filename, sizeof(ic->filename), filename); + pd->filename = ic->filename; + pd->buf = buf; + pd->buf_size = 0; + + if (!fmt) { + /* guess format if no file can be opened */ + fmt = av_probe_input_format(pd, 0); + } + + /* if no file needed do not try to open one */ + if (!fmt || !(fmt->flags & AVFMT_NOFILE)) { + if (url_fopen(&ic->pb, filename, URL_RDONLY) < 0) { + err = AVERROR_IO; + goto fail; + } + if (buf_size > 0) { + url_setbufsize(&ic->pb, buf_size); + } + if (!fmt) { + /* read probe data */ + pd->buf_size = get_buffer(&ic->pb, buf, PROBE_BUF_SIZE); + url_fseek(&ic->pb, 0, SEEK_SET); + } + } + + /* guess file format */ + if (!fmt) { + fmt = av_probe_input_format(pd, 1); + } + + /* if still no format found, error */ + if (!fmt) { + err = AVERROR_NOFMT; + goto fail; + } + + /* XXX: suppress this hack for redirectors */ + if (fmt == &redir_demux) { + err = redir_open(ic_ptr, &ic->pb); + url_fclose(&ic->pb); + av_free(ic); + return err; + } + + ic->iformat = fmt; + + /* allocate private data */ + ic->priv_data = av_mallocz(fmt->priv_data_size); + if (!ic->priv_data) { + err = AVERROR_NOMEM; + goto fail; + } + + /* default pts settings is MPEG like */ + av_set_pts_info(ic, 33, 1, 90000); + + /* check filename in case of an image number is expected */ + if (ic->iformat->flags & AVFMT_NEEDNUMBER) { + if (filename_number_test(ic->filename) < 0) { + err = AVERROR_NUMEXPECTED; + goto fail1; + } + } + + err = ic->iformat->read_header(ic, ap); + if (err < 0) + goto fail1; + *ic_ptr = ic; + return 0; + fail1: + if (!(fmt->flags & AVFMT_NOFILE)) { + url_fclose(&ic->pb); + } + fail: + if (ic) { + av_freep(&ic->priv_data); + } + av_free(ic); + *ic_ptr = NULL; + return err; +} + +/** + * Read a packet from a media file + * @param s media file handle + * @param pkt is filled + * @return 0 if OK. AVERROR_xxx if error. + */ +int av_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVPacketList *pktl; + + pktl = s->packet_buffer; + if (pktl) { + /* read packet from packet buffer, if there is data */ + *pkt = pktl->pkt; + s->packet_buffer = pktl->next; + av_free(pktl); + return 0; + } else { + return s->iformat->read_packet(s, pkt); + } +} + +/* state for codec information */ +#define CSTATE_NOTFOUND 0 +#define CSTATE_DECODING 1 +#define CSTATE_FOUND 2 + +static int has_codec_parameters(AVCodecContext *enc) +{ + int val; + switch(enc->codec_type) { + case CODEC_TYPE_AUDIO: + val = enc->sample_rate; + break; + case CODEC_TYPE_VIDEO: + val = enc->width; + break; + default: + val = 1; + break; + } + return (val != 0); +} + +/** + * Read the beginning of a media file to get stream information. This + * is useful for file formats with no headers such as MPEG. This + * function also compute the real frame rate in case of mpeg2 repeat + * frame mode. + * + * @param ic media file handle + * @return >=0 if OK. AVERROR_xxx if error. + */ +int av_find_stream_info(AVFormatContext *ic) +{ + int i, count, ret, got_picture, size, read_size; + AVCodec *codec; + AVStream *st; + AVPacket *pkt; + AVPicture picture; + AVPacketList *pktl=NULL, **ppktl; + short samples[AVCODEC_MAX_AUDIO_FRAME_SIZE / 2]; + UINT8 *ptr; + int min_read_size, max_read_size; + + /* typical mpeg ts rate is 40 Mbits. DVD rate is about 10 + Mbits. We read at most 0.1 second of file to find all streams */ + + /* XXX: base it on stream bitrate when possible */ + if (ic->iformat == &mpegts_demux) { + /* maximum number of bytes we accept to read to find all the streams + in a file */ + min_read_size = 3000000; + } else { + min_read_size = 125000; + } + /* max read size is 2 seconds of video max */ + max_read_size = min_read_size * 20; + + /* set initial codec state */ + for(i=0;i<ic->nb_streams;i++) { + st = ic->streams[i]; + if (has_codec_parameters(&st->codec)) + st->codec_info_state = CSTATE_FOUND; + else + st->codec_info_state = CSTATE_NOTFOUND; + st->codec_info_nb_repeat_frames = 0; + st->codec_info_nb_real_frames = 0; + } + + count = 0; + read_size = 0; + ppktl = &ic->packet_buffer; + for(;;) { + /* check if one codec still needs to be handled */ + for(i=0;i<ic->nb_streams;i++) { + st = ic->streams[i]; + if (st->codec_info_state != CSTATE_FOUND) + break; + } + if (i == ic->nb_streams) { + /* NOTE: if the format has no header, then we need to read + some packets to get most of the streams, so we cannot + stop here */ + if (!(ic->iformat->flags & AVFMT_NOHEADER) || + read_size >= min_read_size) { + /* if we found the info for all the codecs, we can stop */ + ret = count; + break; + } + } else { + /* we did not get all the codec info, but we read too much data */ + if (read_size >= max_read_size) { + ret = count; + break; + } + } + + pktl = av_mallocz(sizeof(AVPacketList)); + if (!pktl) { + ret = AVERROR_NOMEM; + break; + } + + /* add the packet in the buffered packet list */ + *ppktl = pktl; + ppktl = &pktl->next; + + /* NOTE: a new stream can be added there if no header in file + (AVFMT_NOHEADER) */ + pkt = &pktl->pkt; + if (ic->iformat->read_packet(ic, pkt) < 0) { + /* EOF or error */ + ret = -1; /* we could not have all the codec parameters before EOF */ + if ((ic->iformat->flags & AVFMT_NOHEADER) && + i == ic->nb_streams) + ret = 0; + break; + } + read_size += pkt->size; + + /* open new codecs */ + for(i=0;i<ic->nb_streams;i++) { + st = ic->streams[i]; + if (st->codec_info_state == CSTATE_NOTFOUND) { + /* set to found in case of error */ + st->codec_info_state = CSTATE_FOUND; + codec = avcodec_find_decoder(st->codec.codec_id); + if (codec) { + if(codec->capabilities & CODEC_CAP_TRUNCATED) + st->codec.flags |= CODEC_FLAG_TRUNCATED; + + ret = avcodec_open(&st->codec, codec); + if (ret >= 0) + st->codec_info_state = CSTATE_DECODING; + } + } + } + + st = ic->streams[pkt->stream_index]; + if (st->codec_info_state == CSTATE_DECODING) { + /* decode the data and update codec parameters */ + ptr = pkt->data; + size = pkt->size; + while (size > 0) { + switch(st->codec.codec_type) { + case CODEC_TYPE_VIDEO: + ret = avcodec_decode_video(&st->codec, &picture, + &got_picture, ptr, size); + break; + case CODEC_TYPE_AUDIO: + ret = avcodec_decode_audio(&st->codec, samples, + &got_picture, ptr, size); + break; + default: + ret = -1; + break; + } + if (ret < 0) { + /* if error, simply ignore because another packet + may be OK */ + break; + } + if (got_picture) { + /* we got the parameters - now we can stop + examining this stream */ + /* XXX: add a codec info so that we can decide if + the codec can repeat frames */ + if (st->codec.codec_id == CODEC_ID_MPEG1VIDEO && + ic->iformat != &mpegts_demux && + st->codec.sub_id == 2) { + /* for mpeg2 video, we want to know the real + frame rate, so we decode 40 frames. In mpeg + TS case we do not do it because it would be + too long */ + st->codec_info_nb_real_frames++; + st->codec_info_nb_repeat_frames += st->codec.repeat_pict; +#if 0 + /* XXX: testing */ + if ((st->codec_info_nb_real_frames % 24) == 23) { + st->codec_info_nb_repeat_frames += 2; + } +#endif + /* stop after 40 frames */ + if (st->codec_info_nb_real_frames >= 40) { + st->r_frame_rate = (st->codec.frame_rate * + st->codec_info_nb_real_frames) / + (st->codec_info_nb_real_frames + + (st->codec_info_nb_repeat_frames >> 1)); + goto close_codec; + } + } else { + close_codec: + st->codec_info_state = CSTATE_FOUND; + avcodec_close(&st->codec); + break; + } + } + ptr += ret; + size -= ret; + } + } + count++; + } + + /* close each codec if there are opened */ + for(i=0;i<ic->nb_streams;i++) { + st = ic->streams[i]; + if (st->codec_info_state == CSTATE_DECODING) + avcodec_close(&st->codec); + } + + /* set real frame rate info */ + for(i=0;i<ic->nb_streams;i++) { + st = ic->streams[i]; + if (st->codec.codec_type == CODEC_TYPE_VIDEO) { + if (!st->r_frame_rate) + st->r_frame_rate = st->codec.frame_rate; + } + } + + return ret; +} + +/** + * Close a media file (but not its codecs) + * + * @param s media file handle + */ +void av_close_input_file(AVFormatContext *s) +{ + int i; + + if (s->iformat->read_close) + s->iformat->read_close(s); + for(i=0;i<s->nb_streams;i++) { + av_free(s->streams[i]); + } + if (s->packet_buffer) { + AVPacketList *p, *p1; + p = s->packet_buffer; + while (p != NULL) { + p1 = p->next; + av_free_packet(&p->pkt); + av_free(p); + p = p1; + } + s->packet_buffer = NULL; + } + if (!(s->iformat->flags & AVFMT_NOFILE)) { + url_fclose(&s->pb); + } + av_freep(&s->priv_data); + av_free(s); +} + +/** + * Add a new stream to a media file. Can only be called in the + * read_header function. If the flag AVFMT_NOHEADER is in the format + * description, then new streams can be added in read_packet too. + * + * + * @param s media file handle + * @param id file format dependent stream id + */ +AVStream *av_new_stream(AVFormatContext *s, int id) +{ + AVStream *st; + + if (s->nb_streams >= MAX_STREAMS) + return NULL; + + st = av_mallocz(sizeof(AVStream)); + if (!st) + return NULL; + st->index = s->nb_streams; + st->id = id; + s->streams[s->nb_streams++] = st; + return st; +} + +/************************************************************/ +/* output media file */ + +/** + * allocate the stream private data and write the stream header to an + * output media file + * + * @param s media file handle + * @return 0 if OK. AVERROR_xxx if error. + */ +int av_write_header(AVFormatContext *s) +{ + int ret, i; + AVStream *st; + + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (!s->priv_data) + return AVERROR_NOMEM; + /* default pts settings is MPEG like */ + av_set_pts_info(s, 33, 1, 90000); + ret = s->oformat->write_header(s); + if (ret < 0) + return ret; + + /* init PTS generation */ + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + + switch (st->codec.codec_type) { + case CODEC_TYPE_AUDIO: + av_frac_init(&st->pts, 0, 0, + (INT64)s->pts_num * st->codec.sample_rate); + break; + case CODEC_TYPE_VIDEO: + av_frac_init(&st->pts, 0, 0, + (INT64)s->pts_num * st->codec.frame_rate); + break; + default: + break; + } + } + return 0; +} + +/** + * Write a packet to an output media file. The packet shall contain + * one audio or video frame. + * + * @param s media file handle + * @param stream_index stream index + * @param buf buffer containing the frame data + * @param size size of buffer + * @return < 0 if error, = 0 if OK, 1 if end of stream wanted. + */ +int av_write_frame(AVFormatContext *s, int stream_index, const uint8_t *buf, + int size) +{ + AVStream *st; + INT64 pts_mask; + int ret, frame_size; + + st = s->streams[stream_index]; + pts_mask = (1LL << s->pts_wrap_bits) - 1; + ret = s->oformat->write_packet(s, stream_index, (uint8_t *)buf, size, + st->pts.val & pts_mask); + if (ret < 0) + return ret; + + /* update pts */ + switch (st->codec.codec_type) { + case CODEC_TYPE_AUDIO: + if (st->codec.frame_size <= 1) { + frame_size = size / st->codec.channels; + /* specific hack for pcm codecs because no frame size is provided */ + switch(st->codec.codec_id) { + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_U16BE: + frame_size >>= 1; + break; + default: + break; + } + } else { + frame_size = st->codec.frame_size; + } + av_frac_add(&st->pts, + (INT64)s->pts_den * frame_size); + break; + case CODEC_TYPE_VIDEO: + av_frac_add(&st->pts, + (INT64)s->pts_den * FRAME_RATE_BASE); + break; + default: + break; + } + return ret; +} + +/** + * write the stream trailer to an output media file and and free the + * file private data. + * + * @param s media file handle + * @return 0 if OK. AVERROR_xxx if error. */ +int av_write_trailer(AVFormatContext *s) +{ + int ret; + ret = s->oformat->write_trailer(s); + av_freep(&s->priv_data); + return ret; +} + +/* "user interface" functions */ + +void dump_format(AVFormatContext *ic, + int index, + const char *url, + int is_output) +{ + int i, flags; + char buf[256]; + + fprintf(stderr, "%s #%d, %s, %s '%s':\n", + is_output ? "Output" : "Input", + index, + is_output ? ic->oformat->name : ic->iformat->name, + is_output ? "to" : "from", url); + for(i=0;i<ic->nb_streams;i++) { + AVStream *st = ic->streams[i]; + avcodec_string(buf, sizeof(buf), &st->codec, is_output); + fprintf(stderr, " Stream #%d.%d", index, i); + /* the pid is an important information, so we display it */ + /* XXX: add a generic system */ + if (is_output) + flags = ic->oformat->flags; + else + flags = ic->iformat->flags; + if (flags & AVFMT_SHOW_IDS) { + fprintf(stderr, "[0x%x]", st->id); + } + fprintf(stderr, ": %s\n", buf); + } +} + +typedef struct { + const char *str; + int width, height; +} SizeEntry; + +static SizeEntry sizes[] = { + { "sqcif", 128, 96 }, + { "qcif", 176, 144 }, + { "cif", 352, 288 }, + { "4cif", 704, 576 }, +}; + +int parse_image_size(int *width_ptr, int *height_ptr, const char *str) +{ + int i; + int n = sizeof(sizes) / sizeof(SizeEntry); + const char *p; + int frame_width = 0, frame_height = 0; + + for(i=0;i<n;i++) { + if (!strcmp(sizes[i].str, str)) { + frame_width = sizes[i].width; + frame_height = sizes[i].height; + break; + } + } + if (i == n) { + p = str; + frame_width = strtol(p, (char **)&p, 10); + if (*p) + p++; + frame_height = strtol(p, (char **)&p, 10); + } + if (frame_width <= 0 || frame_height <= 0) + return -1; + *width_ptr = frame_width; + *height_ptr = frame_height; + return 0; +} + +INT64 av_gettime(void) +{ +#ifdef CONFIG_WIN32 + struct _timeb tb; + _ftime(&tb); + return ((INT64)tb.time * INT64_C(1000) + (INT64)tb.millitm) * INT64_C(1000); +#else + struct timeval tv; + gettimeofday(&tv,NULL); + return (INT64)tv.tv_sec * 1000000 + tv.tv_usec; +#endif +} + +static time_t mktimegm(struct tm *tm) +{ + time_t t; + + int y = tm->tm_year + 1900, m = tm->tm_mon + 1, d = tm->tm_mday; + + if (m < 3) { + m += 12; + y--; + } + + t = 86400 * + (d + (153 * m - 457) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 719469); + + t += 3600 * tm->tm_hour + 60 * tm->tm_min + tm->tm_sec; + + return t; +} + +/* Syntax: + * - If not a duration: + * [{YYYY-MM-DD|YYYYMMDD}]{T| }{HH[:MM[:SS[.m...]]][Z]|HH[MM[SS[.m...]]][Z]} + * Time is localtime unless Z is suffixed to the end. In this case GMT + * Return the date in micro seconds since 1970 + * - If duration: + * HH[:MM[:SS[.m...]]] + * S+[.m...] + */ +INT64 parse_date(const char *datestr, int duration) +{ + const char *p; + INT64 t; + struct tm dt; + int i; + static const char *date_fmt[] = { + "%Y-%m-%d", + "%Y%m%d", + }; + static const char *time_fmt[] = { + "%H:%M:%S", + "%H%M%S", + }; + const char *q; + int is_utc, len; + char lastch; + time_t now = time(0); + + len = strlen(datestr); + if (len > 0) + lastch = datestr[len - 1]; + else + lastch = '\0'; + is_utc = (lastch == 'z' || lastch == 'Z'); + + memset(&dt, 0, sizeof(dt)); + + p = datestr; + q = NULL; + if (!duration) { + for (i = 0; i < sizeof(date_fmt) / sizeof(date_fmt[0]); i++) { + q = strptime(p, date_fmt[i], &dt); + if (q) { + break; + } + } + + if (!q) { + if (is_utc) { + dt = *gmtime(&now); + } else { + dt = *localtime(&now); + } + dt.tm_hour = dt.tm_min = dt.tm_sec = 0; + } else { + p = q; + } + + if (*p == 'T' || *p == 't' || *p == ' ') + p++; + + for (i = 0; i < sizeof(time_fmt) / sizeof(time_fmt[0]); i++) { + q = strptime(p, time_fmt[i], &dt); + if (q) { + break; + } + } + } else { + q = strptime(p, time_fmt[0], &dt); + if (!q) { + dt.tm_sec = strtol(p, (char **)&q, 10); + dt.tm_min = 0; + dt.tm_hour = 0; + } + } + + /* Now we have all the fields that we can get */ + if (!q) { + if (duration) + return 0; + else + return now * INT64_C(1000000); + } + + if (duration) { + t = dt.tm_hour * 3600 + dt.tm_min * 60 + dt.tm_sec; + } else { + dt.tm_isdst = -1; /* unknown */ + if (is_utc) { + t = mktimegm(&dt); + } else { + t = mktime(&dt); + } + } + + t *= 1000000; + + if (*q == '.') { + int val, n; + q++; + for (val = 0, n = 100000; n >= 1; n /= 10, q++) { + if (!isdigit(*q)) + break; + val += n * (*q - '0'); + } + t += val; + } + return t; +} + +/* syntax: '?tag1=val1&tag2=val2...'. Little URL decoding is done. Return + 1 if found */ +int find_info_tag(char *arg, int arg_size, const char *tag1, const char *info) +{ + const char *p; + char tag[128], *q; + + p = info; + if (*p == '?') + p++; + for(;;) { + q = tag; + while (*p != '\0' && *p != '=' && *p != '&') { + if ((q - tag) < sizeof(tag) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + q = arg; + if (*p == '=') { + p++; + while (*p != '&' && *p != '\0') { + if ((q - arg) < arg_size - 1) { + if (*p == '+') + *q++ = ' '; + else + *q++ = *p; + } + p++; + } + *q = '\0'; + } + if (!strcmp(tag, tag1)) + return 1; + if (*p != '&') + break; + p++; + } + return 0; +} + +/* Return in 'buf' the path with '%d' replaced by number. Also handles + the '%0nd' format where 'n' is the total number of digits and + '%%'. Return 0 if OK, and -1 if format error */ +int get_frame_filename(char *buf, int buf_size, + const char *path, int number) +{ + const char *p; + char *q, buf1[20]; + int nd, len, c, percentd_found; + + q = buf; + p = path; + percentd_found = 0; + for(;;) { + c = *p++; + if (c == '\0') + break; + if (c == '%') { + nd = 0; + while (*p >= '0' && *p <= '9') { + nd = nd * 10 + *p++ - '0'; + } + c = *p++; + switch(c) { + case '%': + goto addchar; + case 'd': + if (percentd_found) + goto fail; + percentd_found = 1; + snprintf(buf1, sizeof(buf1), "%0*d", nd, number); + len = strlen(buf1); + if ((q - buf + len) > buf_size - 1) + goto fail; + memcpy(q, buf1, len); + q += len; + break; + default: + goto fail; + } + } else { + addchar: + if ((q - buf) < buf_size - 1) + *q++ = c; + } + } + if (!percentd_found) + goto fail; + *q = '\0'; + return 0; + fail: + *q = '\0'; + return -1; +} + +/** + * + * Print on stdout a nice hexa dump of a buffer + * @param buf buffer + * @param size buffer size + */ +void av_hex_dump(UINT8 *buf, int size) +{ + int len, i, j, c; + + for(i=0;i<size;i+=16) { + len = size - i; + if (len > 16) + len = 16; + printf("%08x ", i); + for(j=0;j<16;j++) { + if (j < len) + printf(" %02x", buf[i+j]); + else + printf(" "); + } + printf(" "); + for(j=0;j<len;j++) { + c = buf[i+j]; + if (c < ' ' || c > '~') + c = '.'; + printf("%c", c); + } + printf("\n"); + } +} + +void url_split(char *proto, int proto_size, + char *hostname, int hostname_size, + int *port_ptr, + char *path, int path_size, + const char *url) +{ + const char *p; + char *q; + int port; + + port = -1; + + p = url; + q = proto; + while (*p != ':' && *p != '\0') { + if ((q - proto) < proto_size - 1) + *q++ = *p; + p++; + } + if (proto_size > 0) + *q = '\0'; + if (*p == '\0') { + if (proto_size > 0) + proto[0] = '\0'; + if (hostname_size > 0) + hostname[0] = '\0'; + p = url; + } else { + p++; + if (*p == '/') + p++; + if (*p == '/') + p++; + q = hostname; + while (*p != ':' && *p != '/' && *p != '?' && *p != '\0') { + if ((q - hostname) < hostname_size - 1) + *q++ = *p; + p++; + } + if (hostname_size > 0) + *q = '\0'; + if (*p == ':') { + p++; + port = strtoul(p, (char **)&p, 10); + } + } + if (port_ptr) + *port_ptr = port; + pstrcpy(path, path_size, p); +} + +/** + * Set the pts for a given stream + * @param s stream + * @param pts_wrap_bits number of bits effectively used by the pts + * (used for wrap control, 33 is the value for MPEG) + * @param pts_num numerator to convert to seconds (MPEG: 1) + * @param pts_den denominator to convert to seconds (MPEG: 90000) + */ +void av_set_pts_info(AVFormatContext *s, int pts_wrap_bits, + int pts_num, int pts_den) +{ + s->pts_wrap_bits = pts_wrap_bits; + s->pts_num = pts_num; + s->pts_den = pts_den; +} + +/* fraction handling */ + +/** + * f = val + (num / den) + 0.5. 'num' is normalized so that it is such + * as 0 <= num < den. + * + * @param f fractional number + * @param val integer value + * @param num must be >= 0 + * @param den must be >= 1 + */ +void av_frac_init(AVFrac *f, INT64 val, INT64 num, INT64 den) +{ + num += (den >> 1); + if (num >= den) { + val += num / den; + num = num % den; + } + f->val = val; + f->num = num; + f->den = den; +} + +/* set f to (val + 0.5) */ +void av_frac_set(AVFrac *f, INT64 val) +{ + f->val = val; + f->num = f->den >> 1; +} + +/** + * Fractionnal addition to f: f = f + (incr / f->den) + * + * @param f fractional number + * @param incr increment, can be positive or negative + */ +void av_frac_add(AVFrac *f, INT64 incr) +{ + INT64 num, den; + + num = f->num + incr; + den = f->den; + if (num < 0) { + f->val += num / den; + num = num % den; + if (num < 0) { + num += den; + f->val--; + } + } else if (num >= den) { + f->val += num / den; + num = num % den; + } + f->num = num; +} diff --git a/libavformat/wav.c b/libavformat/wav.c new file mode 100644 index 0000000000..f6bcd7b0e5 --- /dev/null +++ b/libavformat/wav.c @@ -0,0 +1,326 @@ +/* + * WAV encoder and decoder + * Copyright (c) 2001, 2002 Fabrice Bellard. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "avformat.h" +#include "avi.h" + +const CodecTag codec_wav_tags[] = { + { CODEC_ID_MP2, 0x50 }, + { CODEC_ID_MP3LAME, 0x55 }, + { CODEC_ID_AC3, 0x2000 }, + { CODEC_ID_PCM_S16LE, 0x01 }, + { CODEC_ID_PCM_U8, 0x01 }, /* must come after s16le in this list */ + { CODEC_ID_PCM_ALAW, 0x06 }, + { CODEC_ID_PCM_MULAW, 0x07 }, + { CODEC_ID_ADPCM_MS, 0x02 }, + { CODEC_ID_ADPCM_IMA_WAV, 0x11 }, + { CODEC_ID_WMAV1, 0x160 }, + { CODEC_ID_WMAV2, 0x161 }, + { 0, 0 }, +}; + +/* WAVEFORMATEX header */ +/* returns the size or -1 on error */ +int put_wav_header(ByteIOContext *pb, AVCodecContext *enc) +{ + int tag, bps, blkalign, bytespersec; + int hdrsize = 18; + + tag = codec_get_tag(codec_wav_tags, enc->codec_id); + if (tag == 0) + return -1; + put_le16(pb, tag); + put_le16(pb, enc->channels); + put_le32(pb, enc->sample_rate); + if (enc->codec_id == CODEC_ID_PCM_U8 || + enc->codec_id == CODEC_ID_PCM_ALAW || + enc->codec_id == CODEC_ID_PCM_MULAW) { + bps = 8; + } else if (enc->codec_id == CODEC_ID_MP2 || enc->codec_id == CODEC_ID_MP3LAME) { + bps = 0; + } else if (enc->codec_id == CODEC_ID_ADPCM_IMA_WAV || enc->codec_id == CODEC_ID_ADPCM_MS) { + bps = 4; + } else { + bps = 16; + } + + if (enc->codec_id == CODEC_ID_MP2 || enc->codec_id == CODEC_ID_MP3LAME) { + blkalign = 1; + //blkalign = 144 * enc->bit_rate/enc->sample_rate; + } else if (enc->block_align != 0) { /* specified by the codec */ + blkalign = enc->block_align; + } else + blkalign = enc->channels*bps >> 3; + if (enc->codec_id == CODEC_ID_PCM_U8 || + enc->codec_id == CODEC_ID_PCM_S16LE) { + bytespersec = enc->sample_rate * blkalign; + } else { + bytespersec = enc->bit_rate / 8; + } + put_le32(pb, bytespersec); /* bytes per second */ + put_le16(pb, blkalign); /* block align */ + put_le16(pb, bps); /* bits per sample */ + if (enc->codec_id == CODEC_ID_MP3LAME) { + put_le16(pb, 12); /* wav_extra_size */ + hdrsize += 12; + put_le16(pb, 1); /* wID */ + put_le32(pb, 2); /* fdwFlags */ + put_le16(pb, 1152); /* nBlockSize */ + put_le16(pb, 1); /* nFramesPerBlock */ + put_le16(pb, 1393); /* nCodecDelay */ + } else if (enc->codec_id == CODEC_ID_MP2) { + put_le16(pb, 22); /* wav_extra_size */ + hdrsize += 22; + put_le16(pb, 2); /* fwHeadLayer */ + put_le32(pb, enc->bit_rate); /* dwHeadBitrate */ + put_le16(pb, enc->channels == 2 ? 1 : 8); /* fwHeadMode */ + put_le16(pb, 0); /* fwHeadModeExt */ + put_le16(pb, 1); /* wHeadEmphasis */ + put_le16(pb, 16); /* fwHeadFlags */ + put_le32(pb, 0); /* dwPTSLow */ + put_le32(pb, 0); /* dwPTSHigh */ + } else if (enc->codec_id == CODEC_ID_ADPCM_IMA_WAV) { + put_le16(pb, 2); /* wav_extra_size */ + put_le16(pb, ((enc->block_align - 4 * enc->channels) / (4 * enc->channels)) * 8 + 1); /* wSamplesPerBlock */ + } else + put_le16(pb, 0); /* wav_extra_size */ + + return hdrsize; +} + +void get_wav_header(ByteIOContext *pb, AVCodecContext *codec, + int has_extra_data) +{ + int id; + + id = get_le16(pb); + codec->codec_type = CODEC_TYPE_AUDIO; + codec->codec_tag = id; + codec->fourcc = id; + codec->channels = get_le16(pb); + codec->sample_rate = get_le32(pb); + codec->bit_rate = get_le32(pb) * 8; + codec->block_align = get_le16(pb); + codec->frame_bits = get_le16(pb); /* bits per sample */ + codec->codec_id = wav_codec_get_id(id, codec->frame_bits); + if (has_extra_data) { + codec->extradata_size = get_le16(pb); + if (codec->extradata_size > 0) { + codec->extradata = av_mallocz(codec->extradata_size); + get_buffer(pb, codec->extradata, codec->extradata_size); + } + } +} + + +int wav_codec_get_id(unsigned int tag, int bps) +{ + int id; + id = codec_get_id(codec_wav_tags, tag); + if (id <= 0) + return id; + /* handle specific u8 codec */ + if (id == CODEC_ID_PCM_S16LE && bps == 8) + id = CODEC_ID_PCM_U8; + return id; +} + +typedef struct { + offset_t data; +} WAVContext; + +static int wav_write_header(AVFormatContext *s) +{ + WAVContext *wav = s->priv_data; + ByteIOContext *pb = &s->pb; + offset_t fmt; + + put_tag(pb, "RIFF"); + put_le32(pb, 0); /* file length */ + put_tag(pb, "WAVE"); + + /* format header */ + fmt = start_tag(pb, "fmt "); + if (put_wav_header(pb, &s->streams[0]->codec) < 0) { + av_free(wav); + return -1; + } + end_tag(pb, fmt); + + /* data header */ + wav->data = start_tag(pb, "data"); + + put_flush_packet(pb); + + return 0; +} + +static int wav_write_packet(AVFormatContext *s, int stream_index_ptr, + UINT8 *buf, int size, int force_pts) +{ + ByteIOContext *pb = &s->pb; + put_buffer(pb, buf, size); + return 0; +} + +static int wav_write_trailer(AVFormatContext *s) +{ + ByteIOContext *pb = &s->pb; + WAVContext *wav = s->priv_data; + offset_t file_size; + + if (!url_is_streamed(&s->pb)) { + end_tag(pb, wav->data); + + /* update file size */ + file_size = url_ftell(pb); + url_fseek(pb, 4, SEEK_SET); + put_le32(pb, (UINT32)(file_size - 8)); + url_fseek(pb, file_size, SEEK_SET); + + put_flush_packet(pb); + } + return 0; +} + +/* return the size of the found tag */ +/* XXX: > 2GB ? */ +static int find_tag(ByteIOContext *pb, UINT32 tag1) +{ + unsigned int tag; + int size; + + for(;;) { + if (url_feof(pb)) + return -1; + tag = get_le32(pb); + size = get_le32(pb); + if (tag == tag1) + break; + url_fseek(pb, size, SEEK_CUR); + } + if (size < 0) + size = 0x7fffffff; + return size; +} + +static int wav_probe(AVProbeData *p) +{ + /* check file header */ + if (p->buf_size <= 32) + return 0; + if (p->buf[0] == 'R' && p->buf[1] == 'I' && + p->buf[2] == 'F' && p->buf[3] == 'F' && + p->buf[8] == 'W' && p->buf[9] == 'A' && + p->buf[10] == 'V' && p->buf[11] == 'E') + return AVPROBE_SCORE_MAX; + else + return 0; +} + +/* wav input */ +static int wav_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + int size; + unsigned int tag; + ByteIOContext *pb = &s->pb; + AVStream *st; + + /* check RIFF header */ + tag = get_le32(pb); + + if (tag != MKTAG('R', 'I', 'F', 'F')) + return -1; + get_le32(pb); /* file size */ + tag = get_le32(pb); + if (tag != MKTAG('W', 'A', 'V', 'E')) + return -1; + + /* parse fmt header */ + size = find_tag(pb, MKTAG('f', 'm', 't', ' ')); + if (size < 0) + return -1; + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + + get_wav_header(pb, &st->codec, (size >= 18)); + + size = find_tag(pb, MKTAG('d', 'a', 't', 'a')); + if (size < 0) + return -1; + return 0; +} + +#define MAX_SIZE 4096 + +static int wav_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + int ret; + + if (url_feof(&s->pb)) + return -EIO; + if (av_new_packet(pkt, MAX_SIZE)) + return -EIO; + pkt->stream_index = 0; + + ret = get_buffer(&s->pb, pkt->data, pkt->size); + if (ret < 0) + av_free_packet(pkt); + /* note: we need to modify the packet size here to handle the last + packet */ + pkt->size = ret; + return ret; +} + +static int wav_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat wav_iformat = { + "wav", + "wav format", + 0, + wav_probe, + wav_read_header, + wav_read_packet, + wav_read_close, +}; + +static AVOutputFormat wav_oformat = { + "wav", + "wav format", + "audio/x-wav", + "wav", + sizeof(WAVContext), + CODEC_ID_PCM_S16LE, + CODEC_ID_NONE, + wav_write_header, + wav_write_packet, + wav_write_trailer, +}; + +int wav_init(void) +{ + av_register_input_format(&wav_iformat); + av_register_output_format(&wav_oformat); + return 0; +} |