mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-24 16:22:37 +00:00
5e7d21c7ad
Partially undoes commit 2c4e08d893
:
riff: always generate a proper WAVEFORMATEX structure in
ff_put_wav_header
A new flag, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX, is added to force the
use of WAVEFORMATEX rather than PCMWAVEFORMAT even for PCM codecs.
This flag is used in the Matroska muxer (the cause of the original
change) and in the ASF muxer, because the specifications for
these formats indicate explicitly that WAVEFORMATEX should be used.
Muxers for other formats will return to the original behavior of writing
PCMWAVEFORMAT when writing a header for raw PCM.
In particular, this causes raw PCM in WAV to generate the canonical
44-byte header expected by some tools.
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
846 lines
26 KiB
C
846 lines
26 KiB
C
/*
|
|
* Windows Television (WTV) muxer
|
|
* Copyright (c) 2011 Zhentan Feng <spyfeng at gmail dot com>
|
|
* Copyright (c) 2011 Peter Ross <pross@xvid.org>
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Windows Television (WTV) demuxer
|
|
* @author Zhentan Feng <spyfeng at gmail dot com>
|
|
*/
|
|
|
|
#include "libavutil/intreadwrite.h"
|
|
#include "libavutil/avassert.h"
|
|
#include "avformat.h"
|
|
#include "avio_internal.h"
|
|
#include "internal.h"
|
|
#include "mpegts.h"
|
|
#include "wtv.h"
|
|
|
|
#define WTV_BIGSECTOR_SIZE (1 << WTV_BIGSECTOR_BITS)
|
|
#define INDEX_BASE 0x2
|
|
#define MAX_NB_INDEX 10
|
|
|
|
/* declare utf16le strings */
|
|
#define _ , 0,
|
|
static const uint8_t timeline_table_0_header_events[] =
|
|
{'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e'_'.'_'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'E'_'v'_'e'_'n'_'t'_'s', 0};
|
|
static const uint8_t table_0_header_legacy_attrib[] =
|
|
{'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0};
|
|
static const uint8_t table_0_redirector_legacy_attrib[] =
|
|
{'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'r'_'e'_'d'_'i'_'r'_'e'_'c'_'t'_'o'_'r'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0};
|
|
static const uint8_t table_0_header_time[] =
|
|
{'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'t'_'i'_'m'_'e', 0};
|
|
static const uint8_t legacy_attrib[] =
|
|
{'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0};
|
|
#undef _
|
|
|
|
static const ff_asf_guid sub_wtv_guid =
|
|
{0x8C,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D};
|
|
|
|
enum WtvFileIndex {
|
|
WTV_TIMELINE_TABLE_0_HEADER_EVENTS = 0,
|
|
WTV_TIMELINE_TABLE_0_ENTRIES_EVENTS,
|
|
WTV_TIMELINE,
|
|
WTV_TABLE_0_HEADER_LEGACY_ATTRIB,
|
|
WTV_TABLE_0_ENTRIES_LEGACY_ATTRIB,
|
|
WTV_TABLE_0_REDIRECTOR_LEGACY_ATTRIB,
|
|
WTV_TABLE_0_HEADER_TIME,
|
|
WTV_TABLE_0_ENTRIES_TIME,
|
|
WTV_FILES
|
|
};
|
|
|
|
typedef struct {
|
|
int64_t length;
|
|
const void *header;
|
|
int depth;
|
|
int first_sector;
|
|
} WtvFile;
|
|
|
|
typedef struct {
|
|
int64_t pos;
|
|
int64_t serial;
|
|
const ff_asf_guid * guid;
|
|
int stream_id;
|
|
} WtvChunkEntry;
|
|
|
|
typedef struct {
|
|
int64_t serial;
|
|
int64_t value;
|
|
} WtvSyncEntry;
|
|
|
|
typedef struct {
|
|
int64_t timeline_start_pos;
|
|
WtvFile file[WTV_FILES];
|
|
int64_t serial; /**< chunk serial number */
|
|
int64_t last_chunk_pos; /**< last chunk position */
|
|
int64_t last_timestamp_pos; /**< last timestamp chunk position */
|
|
int64_t first_index_pos; /**< first index_chunk position */
|
|
|
|
WtvChunkEntry index[MAX_NB_INDEX];
|
|
int nb_index;
|
|
int first_video_flag;
|
|
|
|
WtvSyncEntry *st_pairs; /* (serial, timestamp) pairs */
|
|
int nb_st_pairs;
|
|
WtvSyncEntry *sp_pairs; /* (serial, position) pairs */
|
|
int nb_sp_pairs;
|
|
|
|
int64_t last_pts;
|
|
int64_t last_serial;
|
|
|
|
AVPacket thumbnail;
|
|
} WtvContext;
|
|
|
|
|
|
static void add_serial_pair(WtvSyncEntry ** list, int * count, int64_t serial, int64_t value)
|
|
{
|
|
int new_count = *count + 1;
|
|
WtvSyncEntry *new_list = av_realloc(*list, new_count * sizeof(WtvSyncEntry));
|
|
if (!new_list)
|
|
return;
|
|
new_list[*count] = (WtvSyncEntry){serial, value};
|
|
*list = new_list;
|
|
*count = new_count;
|
|
}
|
|
|
|
typedef int WTVHeaderWriteFunc(AVIOContext *pb);
|
|
|
|
typedef struct {
|
|
const uint8_t *header;
|
|
int header_size;
|
|
WTVHeaderWriteFunc *write_header;
|
|
} WTVRootEntryTable;
|
|
|
|
#define write_pad(pb, size) ffio_fill(pb, 0, size)
|
|
|
|
/**
|
|
* Write chunk header. If header chunk (0x80000000 set) then add to list of header chunks
|
|
*/
|
|
static void write_chunk_header(AVFormatContext *s, const ff_asf_guid *guid, int length, int stream_id)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
|
|
wctx->last_chunk_pos = avio_tell(pb) - wctx->timeline_start_pos;
|
|
ff_put_guid(pb, guid);
|
|
avio_wl32(pb, 32 + length);
|
|
avio_wl32(pb, stream_id);
|
|
avio_wl64(pb, wctx->serial);
|
|
|
|
if ((stream_id & 0x80000000) && guid != &ff_index_guid) {
|
|
WtvChunkEntry *t = wctx->index + wctx->nb_index;
|
|
av_assert0(wctx->nb_index < MAX_NB_INDEX);
|
|
t->pos = wctx->last_chunk_pos;
|
|
t->serial = wctx->serial;
|
|
t->guid = guid;
|
|
t->stream_id = stream_id & 0x3FFFFFFF;
|
|
wctx->nb_index++;
|
|
}
|
|
}
|
|
|
|
static void write_chunk_header2(AVFormatContext *s, const ff_asf_guid *guid, int stream_id)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
|
|
int64_t last_chunk_pos = wctx->last_chunk_pos;
|
|
write_chunk_header(s, guid, 0, stream_id); // length updated later
|
|
avio_wl64(pb, last_chunk_pos);
|
|
}
|
|
|
|
static void finish_chunk_noindex(AVFormatContext *s)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
|
|
// update the chunk_len field and pad.
|
|
int64_t chunk_len = avio_tell(pb) - (wctx->last_chunk_pos + wctx->timeline_start_pos);
|
|
avio_seek(pb, -(chunk_len - 16), SEEK_CUR);
|
|
avio_wl32(pb, chunk_len);
|
|
avio_seek(pb, chunk_len - (16 + 4), SEEK_CUR);
|
|
|
|
write_pad(pb, WTV_PAD8(chunk_len) - chunk_len);
|
|
wctx->serial++;
|
|
}
|
|
|
|
static void write_index(AVFormatContext *s)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int i;
|
|
|
|
write_chunk_header2(s, &ff_index_guid, 0x80000000);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
|
|
for (i = 0; i < wctx->nb_index; i++) {
|
|
WtvChunkEntry *t = wctx->index + i;
|
|
ff_put_guid(pb, t->guid);
|
|
avio_wl64(pb, t->pos);
|
|
avio_wl32(pb, t->stream_id);
|
|
avio_wl32(pb, 0); // checksum?
|
|
avio_wl64(pb, t->serial);
|
|
}
|
|
wctx->nb_index = 0; // reset index
|
|
finish_chunk_noindex(s);
|
|
|
|
if (!wctx->first_index_pos)
|
|
wctx->first_index_pos = wctx->last_chunk_pos;
|
|
}
|
|
|
|
static void finish_chunk(AVFormatContext *s)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
finish_chunk_noindex(s);
|
|
if (wctx->nb_index == MAX_NB_INDEX)
|
|
write_index(s);
|
|
}
|
|
|
|
static void put_videoinfoheader2(AVIOContext *pb, AVStream *st)
|
|
{
|
|
AVRational dar = av_mul_q(st->sample_aspect_ratio, (AVRational){st->codec->width, st->codec->height});
|
|
unsigned int num, den;
|
|
av_reduce(&num, &den, dar.num, dar.den, 0xFFFFFFFF);
|
|
|
|
/* VIDEOINFOHEADER2 */
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, st->codec->width);
|
|
avio_wl32(pb, st->codec->height);
|
|
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
|
|
avio_wl32(pb, st->codec->bit_rate);
|
|
avio_wl32(pb, 0);
|
|
avio_wl64(pb, st->avg_frame_rate.num && st->avg_frame_rate.den ? INT64_C(10000000) / av_q2d(st->avg_frame_rate) : 0);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
|
|
avio_wl32(pb, num);
|
|
avio_wl32(pb, den);
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, 0);
|
|
|
|
ff_put_bmp_header(pb, st->codec, ff_codec_bmp_tags, 0, 1);
|
|
|
|
if (st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
|
|
int padding = (st->codec->extradata_size & 3) ? 4 - (st->codec->extradata_size & 3) : 0;
|
|
/* MPEG2VIDEOINFO */
|
|
avio_wl32(pb, 0);
|
|
avio_wl32(pb, st->codec->extradata_size + padding);
|
|
avio_wl32(pb, -1);
|
|
avio_wl32(pb, -1);
|
|
avio_wl32(pb, 0);
|
|
avio_write(pb, st->codec->extradata, st->codec->extradata_size);
|
|
ffio_fill(pb, 0, padding);
|
|
}
|
|
}
|
|
|
|
static int write_stream_codec_info(AVFormatContext *s, AVStream *st)
|
|
{
|
|
const ff_asf_guid *g, *media_type, *format_type;
|
|
const AVCodecTag *tags;
|
|
AVIOContext *pb = s->pb;
|
|
int64_t hdr_pos_start;
|
|
int hdr_size = 0;
|
|
|
|
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
g = get_codec_guid(st->codec->codec_id, ff_video_guids);
|
|
media_type = &ff_mediatype_video;
|
|
format_type = st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO ? &ff_format_mpeg2_video : &ff_format_videoinfo2;
|
|
tags = ff_codec_bmp_tags;
|
|
} else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
g = get_codec_guid(st->codec->codec_id, ff_codec_wav_guids);
|
|
media_type = &ff_mediatype_audio;
|
|
format_type = &ff_format_waveformatex;
|
|
tags = ff_codec_wav_tags;
|
|
} else {
|
|
av_log(s, AV_LOG_ERROR, "unknown codec_type (0x%x)\n", st->codec->codec_type);
|
|
return -1;
|
|
}
|
|
|
|
ff_put_guid(pb, media_type); // mediatype
|
|
ff_put_guid(pb, &ff_mediasubtype_cpfilters_processed); // subtype
|
|
write_pad(pb, 12);
|
|
ff_put_guid(pb,&ff_format_cpfilters_processed); // format type
|
|
avio_wl32(pb, 0); // size
|
|
|
|
hdr_pos_start = avio_tell(pb);
|
|
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
put_videoinfoheader2(pb, st);
|
|
} else {
|
|
if (ff_put_wav_header(pb, st->codec, 0) < 0)
|
|
format_type = &ff_format_none;
|
|
}
|
|
hdr_size = avio_tell(pb) - hdr_pos_start;
|
|
|
|
// seek back write hdr_size
|
|
avio_seek(pb, -(hdr_size + 4), SEEK_CUR);
|
|
avio_wl32(pb, hdr_size + 32);
|
|
avio_seek(pb, hdr_size, SEEK_CUR);
|
|
if (g) {
|
|
ff_put_guid(pb, g); // actual_subtype
|
|
} else {
|
|
int tag = ff_codec_get_tag(tags, st->codec->codec_id);
|
|
if (!tag) {
|
|
av_log(s, AV_LOG_ERROR, "unsupported codec_id (0x%x)\n", st->codec->codec_id);
|
|
return -1;
|
|
}
|
|
avio_wl32(pb, tag);
|
|
avio_write(pb, (const uint8_t[]){FF_MEDIASUBTYPE_BASE_GUID}, 12);
|
|
}
|
|
ff_put_guid(pb, format_type); // actual_formattype
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_stream_codec(AVFormatContext *s, AVStream * st)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
int ret;
|
|
write_chunk_header2(s, &ff_stream1_guid, 0x80000000 | 0x01);
|
|
|
|
avio_wl32(pb, 0x01);
|
|
write_pad(pb, 4);
|
|
write_pad(pb, 4);
|
|
|
|
ret = write_stream_codec_info(s, st);
|
|
if (ret < 0) {
|
|
av_log(s, AV_LOG_ERROR, "write stream codec info failed codec_type(0x%x)\n", st->codec->codec_type);
|
|
return -1;
|
|
}
|
|
|
|
finish_chunk(s);
|
|
return 0;
|
|
}
|
|
|
|
static void write_sync(AVFormatContext *s)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int64_t last_chunk_pos = wctx->last_chunk_pos;
|
|
|
|
write_chunk_header(s, &ff_sync_guid, 0x18, 0);
|
|
avio_wl64(pb, wctx->first_index_pos);
|
|
avio_wl64(pb, wctx->last_timestamp_pos);
|
|
avio_wl64(pb, 0);
|
|
|
|
finish_chunk(s);
|
|
add_serial_pair(&wctx->sp_pairs, &wctx->nb_sp_pairs, wctx->serial, wctx->last_chunk_pos);
|
|
|
|
wctx->last_chunk_pos = last_chunk_pos;
|
|
}
|
|
|
|
static int write_stream_data(AVFormatContext *s, AVStream *st)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
int ret;
|
|
|
|
write_chunk_header2(s, &ff_SBE2_STREAM_DESC_EVENT, 0x80000000 | (st->index + INDEX_BASE));
|
|
avio_wl32(pb, 0x00000001);
|
|
avio_wl32(pb, st->index + INDEX_BASE); //stream_id
|
|
avio_wl32(pb, 0x00000001);
|
|
write_pad(pb, 8);
|
|
|
|
ret = write_stream_codec_info(s, st);
|
|
if (ret < 0) {
|
|
av_log(s, AV_LOG_ERROR, "write stream codec info failed codec_type(0x%x)\n", st->codec->codec_type);
|
|
return -1;
|
|
}
|
|
finish_chunk(s);
|
|
|
|
avpriv_set_pts_info(st, 64, 1, 10000000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_header(AVFormatContext *s)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int i, pad, ret;
|
|
AVStream *st;
|
|
|
|
wctx->last_chunk_pos = -1;
|
|
wctx->last_timestamp_pos = -1;
|
|
|
|
ff_put_guid(pb, &ff_wtv_guid);
|
|
ff_put_guid(pb, &sub_wtv_guid);
|
|
|
|
avio_wl32(pb, 0x01);
|
|
avio_wl32(pb, 0x02);
|
|
avio_wl32(pb, 1 << WTV_SECTOR_BITS);
|
|
avio_wl32(pb, 1 << WTV_BIGSECTOR_BITS);
|
|
|
|
//write initial root fields
|
|
avio_wl32(pb, 0); // root_size, update later
|
|
write_pad(pb, 4);
|
|
avio_wl32(pb, 0); // root_sector, update it later.
|
|
|
|
write_pad(pb, 32);
|
|
avio_wl32(pb, 0); // file ends pointer, update it later.
|
|
|
|
pad = (1 << WTV_SECTOR_BITS) - avio_tell(pb);
|
|
write_pad(pb, pad);
|
|
|
|
wctx->timeline_start_pos = avio_tell(pb);
|
|
|
|
wctx->serial = 1;
|
|
wctx->last_chunk_pos = -1;
|
|
wctx->first_video_flag = 1;
|
|
|
|
for (i = 0; i < s->nb_streams; i++) {
|
|
st = s->streams[i];
|
|
if (st->codec->codec_id == AV_CODEC_ID_MJPEG)
|
|
continue;
|
|
ret = write_stream_codec(s, st);
|
|
if (ret < 0) {
|
|
av_log(s, AV_LOG_ERROR, "write stream codec failed codec_type(0x%x)\n", st->codec->codec_type);
|
|
return -1;
|
|
}
|
|
if (!i)
|
|
write_sync(s);
|
|
}
|
|
|
|
for (i = 0; i < s->nb_streams; i++) {
|
|
st = s->streams[i];
|
|
if (st->codec->codec_id == AV_CODEC_ID_MJPEG)
|
|
continue;
|
|
ret = write_stream_data(s, st);
|
|
if (ret < 0) {
|
|
av_log(s, AV_LOG_ERROR, "write stream data failed codec_type(0x%x)\n", st->codec->codec_type);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (wctx->nb_index)
|
|
write_index(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void write_timestamp(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
AVCodecContext *enc = s->streams[pkt->stream_index]->codec;
|
|
|
|
write_chunk_header(s, &ff_timestamp_guid, 56, 0x40000000 | (INDEX_BASE + pkt->stream_index));
|
|
write_pad(pb, 8);
|
|
avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts);
|
|
avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts);
|
|
avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts);
|
|
avio_wl64(pb, 0);
|
|
avio_wl64(pb, enc->codec_type == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY) ? 1 : 0);
|
|
avio_wl64(pb, 0);
|
|
|
|
wctx->last_timestamp_pos = wctx->last_chunk_pos;
|
|
}
|
|
|
|
static int write_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
AVStream *st = s->streams[pkt->stream_index];
|
|
|
|
if (st->codec->codec_id == AV_CODEC_ID_MJPEG && !wctx->thumbnail.size) {
|
|
av_copy_packet(&wctx->thumbnail, pkt);
|
|
return 0;
|
|
} else if (st->codec->codec_id == AV_CODEC_ID_H264) {
|
|
int ret = ff_check_h264_startcode(s, st, pkt);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* emit sync chunk and 'timeline.table.0.entries.Event' record every 50 frames */
|
|
if (wctx->serial - (wctx->nb_sp_pairs ? wctx->sp_pairs[wctx->nb_sp_pairs - 1].serial : 0) >= 50)
|
|
write_sync(s);
|
|
|
|
/* emit 'table.0.entries.time' record every 500ms */
|
|
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts - (wctx->nb_st_pairs ? wctx->st_pairs[wctx->nb_st_pairs - 1].value : 0) >= 5000000)
|
|
add_serial_pair(&wctx->st_pairs, &wctx->nb_st_pairs, wctx->serial, pkt->pts);
|
|
|
|
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts > wctx->last_pts) {
|
|
wctx->last_pts = pkt->pts;
|
|
wctx->last_serial = wctx->serial;
|
|
}
|
|
|
|
// write timestamp chunk
|
|
write_timestamp(s, pkt);
|
|
|
|
write_chunk_header(s, &ff_data_guid, pkt->size, INDEX_BASE + pkt->stream_index);
|
|
avio_write(pb, pkt->data, pkt->size);
|
|
write_pad(pb, WTV_PAD8(pkt->size) - pkt->size);
|
|
|
|
wctx->serial++;
|
|
return 0;
|
|
}
|
|
|
|
static int write_table0_header_events(AVIOContext *pb)
|
|
{
|
|
avio_wl32(pb, 0x10);
|
|
write_pad(pb, 84);
|
|
avio_wl64(pb, 0x32);
|
|
return 96;
|
|
}
|
|
|
|
static int write_table0_header_legacy_attrib(AVIOContext *pb)
|
|
{
|
|
int pad = 0;
|
|
avio_wl32(pb, 0xFFFFFFFF);
|
|
write_pad(pb, 12);
|
|
avio_write(pb, legacy_attrib, sizeof(legacy_attrib));
|
|
pad = WTV_PAD8(sizeof(legacy_attrib)) - sizeof(legacy_attrib);
|
|
write_pad(pb, pad);
|
|
write_pad(pb, 32);
|
|
return 48 + WTV_PAD8(sizeof(legacy_attrib));
|
|
}
|
|
|
|
static int write_table0_header_time(AVIOContext *pb)
|
|
{
|
|
avio_wl32(pb, 0x10);
|
|
write_pad(pb, 76);
|
|
avio_wl64(pb, 0x40);
|
|
return 88;
|
|
}
|
|
|
|
static const WTVRootEntryTable wtv_root_entry_table[] = {
|
|
{ timeline_table_0_header_events, sizeof(timeline_table_0_header_events), write_table0_header_events},
|
|
{ ff_timeline_table_0_entries_Events_le16, sizeof(ff_timeline_table_0_entries_Events_le16), NULL},
|
|
{ ff_timeline_le16, sizeof(ff_timeline_le16), NULL},
|
|
{ table_0_header_legacy_attrib, sizeof(table_0_header_legacy_attrib), write_table0_header_legacy_attrib},
|
|
{ ff_table_0_entries_legacy_attrib_le16, sizeof(ff_table_0_entries_legacy_attrib_le16), NULL},
|
|
{ table_0_redirector_legacy_attrib, sizeof(table_0_redirector_legacy_attrib), NULL},
|
|
{ table_0_header_time, sizeof(table_0_header_time), write_table0_header_time},
|
|
{ ff_table_0_entries_time_le16, sizeof(ff_table_0_entries_time_le16), NULL},
|
|
};
|
|
|
|
static int write_root_table(AVFormatContext *s, int64_t sector_pos)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int size, pad;
|
|
int i;
|
|
|
|
const WTVRootEntryTable *h = wtv_root_entry_table;
|
|
for (i = 0; i < sizeof(wtv_root_entry_table)/sizeof(WTVRootEntryTable); i++, h++) {
|
|
WtvFile *w = &wctx->file[i];
|
|
int filename_padding = WTV_PAD8(h->header_size) - h->header_size;
|
|
WTVHeaderWriteFunc *write = h->write_header;
|
|
int len = 0;
|
|
int64_t len_pos;
|
|
|
|
ff_put_guid(pb, &ff_dir_entry_guid);
|
|
len_pos = avio_tell(pb);
|
|
avio_wl16(pb, 40 + h->header_size + filename_padding + 8); // maybe updated later
|
|
write_pad(pb, 6);
|
|
avio_wl64(pb, write ? 0 : w->length);// maybe update later
|
|
avio_wl32(pb, (h->header_size + filename_padding) >> 1);
|
|
write_pad(pb, 4);
|
|
|
|
avio_write(pb, h->header, h->header_size);
|
|
write_pad(pb, filename_padding);
|
|
|
|
if (write) {
|
|
len = write(pb);
|
|
// update length field
|
|
avio_seek(pb, len_pos, SEEK_SET);
|
|
avio_wl64(pb, 40 + h->header_size + filename_padding + len);
|
|
avio_wl64(pb, len |(1ULL<<62) | (1ULL<<60));
|
|
avio_seek(pb, 8 + h->header_size + filename_padding + len, SEEK_CUR);
|
|
} else {
|
|
avio_wl32(pb, w->first_sector);
|
|
avio_wl32(pb, w->depth);
|
|
}
|
|
}
|
|
|
|
// caculate root table size
|
|
size = avio_tell(pb) - sector_pos;
|
|
pad = WTV_SECTOR_SIZE- size;
|
|
write_pad(pb, pad);
|
|
|
|
return size;
|
|
}
|
|
|
|
static void write_fat(AVIOContext *pb, int start_sector, int nb_sectors, int shift)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nb_sectors; i++) {
|
|
avio_wl32(pb, start_sector + (i << shift));
|
|
}
|
|
// pad left sector pointer size
|
|
write_pad(pb, WTV_SECTOR_SIZE - ((nb_sectors << 2) % WTV_SECTOR_SIZE));
|
|
}
|
|
|
|
static int64_t write_fat_sector(AVFormatContext *s, int64_t start_pos, int nb_sectors, int sector_bits, int depth)
|
|
{
|
|
int64_t start_sector = start_pos >> WTV_SECTOR_BITS;
|
|
int shift = sector_bits - WTV_SECTOR_BITS;
|
|
|
|
int64_t fat = avio_tell(s->pb);
|
|
write_fat(s->pb, start_sector, nb_sectors, shift);
|
|
|
|
if (depth == 2) {
|
|
int64_t start_sector1 = fat >> WTV_SECTOR_BITS;
|
|
int nb_sectors1 = ((nb_sectors << 2) + WTV_SECTOR_SIZE - 1) / WTV_SECTOR_SIZE;
|
|
int64_t fat1 = avio_tell(s->pb);
|
|
|
|
write_fat(s->pb, start_sector1, nb_sectors1, 0);
|
|
return fat1;
|
|
}
|
|
|
|
return fat;
|
|
}
|
|
|
|
static void write_table_entries_events(AVFormatContext *s)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int i;
|
|
for (i = 0; i < wctx->nb_sp_pairs; i++) {
|
|
avio_wl64(pb, wctx->sp_pairs[i].serial);
|
|
avio_wl64(pb, wctx->sp_pairs[i].value);
|
|
}
|
|
}
|
|
|
|
static void write_table_entries_time(AVFormatContext *s)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
WtvContext *wctx = s->priv_data;
|
|
int i;
|
|
for (i = 0; i < wctx->nb_st_pairs; i++) {
|
|
avio_wl64(pb, wctx->st_pairs[i].value);
|
|
avio_wl64(pb, wctx->st_pairs[i].serial);
|
|
}
|
|
avio_wl64(pb, wctx->last_pts);
|
|
avio_wl64(pb, wctx->last_serial);
|
|
}
|
|
|
|
static void write_metadata_header(AVIOContext *pb, int type, const char *key, int value_size)
|
|
{
|
|
ff_put_guid(pb, &ff_metadata_guid);
|
|
avio_wl32(pb, type);
|
|
avio_wl32(pb, value_size);
|
|
avio_put_str16le(pb, key);
|
|
}
|
|
|
|
static int metadata_header_size(const char *key)
|
|
{
|
|
return 16 + 4 + 4 + strlen(key)*2 + 2;
|
|
}
|
|
|
|
static void write_tag_int32(AVIOContext *pb, const char *key, int value)
|
|
{
|
|
write_metadata_header(pb, 0, key, 4);
|
|
avio_wl32(pb, value);
|
|
}
|
|
|
|
static void write_tag(AVIOContext *pb, const char *key, const char *value)
|
|
{
|
|
write_metadata_header(pb, 1, key, strlen(value)*2 + 2);
|
|
avio_put_str16le(pb, value);
|
|
}
|
|
|
|
static int attachment_value_size(const AVPacket *pkt, const AVDictionaryEntry *e)
|
|
{
|
|
return strlen("image/jpeg")*2 + 2 + 1 + (e ? strlen(e->value)*2 : 0) + 2 + 4 + pkt->size;
|
|
}
|
|
|
|
static void write_table_entries_attrib(AVFormatContext *s)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
AVDictionaryEntry *tag = 0;
|
|
|
|
//FIXME: translate special tags (e.g. WM/Bitrate) to binary representation
|
|
ff_metadata_conv(&s->metadata, ff_asf_metadata_conv, NULL);
|
|
while ((tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
|
write_tag(pb, tag->key, tag->value);
|
|
|
|
if (wctx->thumbnail.size) {
|
|
AVStream *st = s->streams[wctx->thumbnail.stream_index];
|
|
tag = av_dict_get(st->metadata, "title", NULL, 0);
|
|
write_metadata_header(pb, 2, "WM/Picture", attachment_value_size(&wctx->thumbnail, tag));
|
|
|
|
avio_put_str16le(pb, "image/jpeg");
|
|
avio_w8(pb, 0x10);
|
|
avio_put_str16le(pb, tag ? tag->value : "");
|
|
|
|
avio_wl32(pb, wctx->thumbnail.size);
|
|
avio_write(pb, wctx->thumbnail.data, wctx->thumbnail.size);
|
|
|
|
write_tag_int32(pb, "WM/MediaThumbType", 2);
|
|
}
|
|
}
|
|
|
|
static void write_table_redirector_legacy_attrib(AVFormatContext *s)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
AVDictionaryEntry *tag = 0;
|
|
int64_t pos = 0;
|
|
|
|
//FIXME: translate special tags to binary representation
|
|
while ((tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
|
avio_wl64(pb, pos);
|
|
pos += metadata_header_size(tag->key) + strlen(tag->value)*2 + 2;
|
|
}
|
|
|
|
if (wctx->thumbnail.size) {
|
|
AVStream *st = s->streams[wctx->thumbnail.stream_index];
|
|
avio_wl64(pb, pos);
|
|
pos += metadata_header_size("WM/Picture") + attachment_value_size(&wctx->thumbnail, av_dict_get(st->metadata, "title", NULL, 0));
|
|
|
|
avio_wl64(pb, pos);
|
|
pos += metadata_header_size("WM/MediaThumbType") + 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pad the remainder of a file
|
|
* Write out fat table
|
|
* @return <0 on error
|
|
*/
|
|
static int finish_file(AVFormatContext *s, enum WtvFileIndex index, int64_t start_pos)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
WtvFile *w = &wctx->file[index];
|
|
int64_t end_pos = avio_tell(pb);
|
|
int sector_bits, nb_sectors, pad;
|
|
|
|
av_assert0(index < WTV_FILES);
|
|
|
|
w->length = (end_pos - start_pos);
|
|
|
|
// determine optimal fat table depth, sector_bits, nb_sectors
|
|
if (w->length <= WTV_SECTOR_SIZE) {
|
|
w->depth = 0;
|
|
sector_bits = WTV_SECTOR_BITS;
|
|
} else if (w->length <= (WTV_SECTOR_SIZE / 4) * WTV_SECTOR_SIZE) {
|
|
w->depth = 1;
|
|
sector_bits = WTV_SECTOR_BITS;
|
|
} else if (w->length <= (WTV_SECTOR_SIZE / 4) * WTV_BIGSECTOR_SIZE) {
|
|
w->depth = 1;
|
|
sector_bits = WTV_BIGSECTOR_BITS;
|
|
} else if (w->length <= (int64_t)(WTV_SECTOR_SIZE / 4) * (WTV_SECTOR_SIZE / 4) * WTV_SECTOR_SIZE) {
|
|
w->depth = 2;
|
|
sector_bits = WTV_SECTOR_BITS;
|
|
} else if (w->length <= (int64_t)(WTV_SECTOR_SIZE / 4) * (WTV_SECTOR_SIZE / 4) * WTV_BIGSECTOR_SIZE) {
|
|
w->depth = 2;
|
|
sector_bits = WTV_BIGSECTOR_BITS;
|
|
} else {
|
|
av_log(s, AV_LOG_ERROR, "unsupported file allocation table depth (%"PRIi64" bytes)\n", w->length);
|
|
return -1;
|
|
}
|
|
|
|
// determine the nb_sectors
|
|
nb_sectors = (int)(w->length >> sector_bits);
|
|
|
|
// pad sector of timeline
|
|
pad = (1 << sector_bits) - (w->length % (1 << sector_bits));
|
|
if (pad) {
|
|
nb_sectors++;
|
|
write_pad(pb, pad);
|
|
}
|
|
|
|
//write fat table
|
|
if (w->depth > 0) {
|
|
w->first_sector = write_fat_sector(s, start_pos, nb_sectors, sector_bits, w->depth) >> WTV_SECTOR_BITS;
|
|
} else {
|
|
w->first_sector = start_pos >> WTV_SECTOR_BITS;
|
|
}
|
|
|
|
w->length |= 1ULL<<60;
|
|
if (sector_bits == WTV_SECTOR_BITS)
|
|
w->length |= 1ULL<<63;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_trailer(AVFormatContext *s)
|
|
{
|
|
WtvContext *wctx = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int root_size;
|
|
int64_t sector_pos;
|
|
int64_t start_pos, file_end_pos;
|
|
|
|
if (finish_file(s, WTV_TIMELINE, wctx->timeline_start_pos) < 0)
|
|
return -1;
|
|
|
|
start_pos = avio_tell(pb);
|
|
write_table_entries_events(s);
|
|
if (finish_file(s, WTV_TIMELINE_TABLE_0_ENTRIES_EVENTS, start_pos) < 0)
|
|
return -1;
|
|
|
|
start_pos = avio_tell(pb);
|
|
write_table_entries_attrib(s);
|
|
if (finish_file(s, WTV_TABLE_0_ENTRIES_LEGACY_ATTRIB, start_pos) < 0)
|
|
return -1;
|
|
|
|
start_pos = avio_tell(pb);
|
|
write_table_redirector_legacy_attrib(s);
|
|
if (finish_file(s, WTV_TABLE_0_REDIRECTOR_LEGACY_ATTRIB, start_pos) < 0)
|
|
return -1;
|
|
|
|
start_pos = avio_tell(pb);
|
|
write_table_entries_time(s);
|
|
if (finish_file(s, WTV_TABLE_0_ENTRIES_TIME, start_pos) < 0)
|
|
return -1;
|
|
|
|
// write root table
|
|
sector_pos = avio_tell(pb);
|
|
root_size = write_root_table(s, sector_pos);
|
|
|
|
file_end_pos = avio_tell(pb);
|
|
// update root value
|
|
avio_seek(pb, 0x30, SEEK_SET);
|
|
avio_wl32(pb, root_size);
|
|
avio_seek(pb, 4, SEEK_CUR);
|
|
avio_wl32(pb, sector_pos >> WTV_SECTOR_BITS);
|
|
avio_seek(pb, 0x5c, SEEK_SET);
|
|
avio_wl32(pb, file_end_pos >> WTV_SECTOR_BITS);
|
|
|
|
avio_flush(pb);
|
|
|
|
av_free(wctx->sp_pairs);
|
|
av_free(wctx->st_pairs);
|
|
av_free_packet(&wctx->thumbnail);
|
|
return 0;
|
|
}
|
|
|
|
AVOutputFormat ff_wtv_muxer = {
|
|
.name = "wtv",
|
|
.long_name = NULL_IF_CONFIG_SMALL("Windows Television (WTV)"),
|
|
.extensions = "wtv",
|
|
.priv_data_size = sizeof(WtvContext),
|
|
.audio_codec = AV_CODEC_ID_AC3,
|
|
.video_codec = AV_CODEC_ID_MPEG2VIDEO,
|
|
.write_header = write_header,
|
|
.write_packet = write_packet,
|
|
.write_trailer = write_trailer,
|
|
.codec_tag = (const AVCodecTag* const []){ ff_codec_bmp_tags,
|
|
ff_codec_wav_tags, 0 },
|
|
};
|