FLV: implement Macromedia Flash Video reader

Implements #735.
This commit is contained in:
Moritz Bunkus 2012-12-21 11:00:35 +01:00
parent b57b7f8f71
commit 8376df5900
11 changed files with 1189 additions and 22 deletions

View File

@ -1,3 +1,8 @@
2012-12-23 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: new feature: Implemented a reader for the Macromedia
Flash Video format (.flv). Implements #735.
2012-12-22 Moritz Bunkus <moritz@bunkus.org>
* Build system: Boost's "variant" library is now required.

147
src/common/amf.cpp Normal file
View File

@ -0,0 +1,147 @@
/*
mkvmerge -- utility for splicing together matroska files
from component media subtypes
Distributed under the GPL
see the file COPYING for details
or visit http://www.gnu.org/copyleft/gpl.html
helper functions for AMF (Adobe Macromedia Flash) data
Written by Moritz Bunkus <moritz@bunkus.org>.
*/
#include "common/common_pch.h"
#include "common/amf.h"
#include "common/math.h"
#include "common/mm_io_x.h"
namespace mtx { namespace amf {
script_parser_c::script_parser_c(memory_cptr const &mem)
: m_data_mem{mem}
, m_data{*mem.get()}
, m_in_ecma_array{}
, m_in_meta_data{}
, m_level{}
, m_debug{debugging_requested("amf|amf_script_parser")}
{
}
std::string
script_parser_c::level_spacer()
const {
return std::string(m_level, ' ');
}
std::unordered_map<std::string, value_type_t> const &
script_parser_c::get_meta_data()
const {
return m_meta_data;
}
std::string
script_parser_c::read_string(data_type_e type) {
unsigned int size = TYPE_STRING == type ? m_data.read_uint16_be() : m_data.read_uint32_be();
std::string data;
if (!size)
m_data.skip(1);
else if (m_data.read(data, size) != size)
throw mtx::mm_io::end_of_file_x{};
while ((0 < size) && !data[size - 1])
--size;
data.erase(size);
return data;
}
void
script_parser_c::read_properties(std::unordered_map<std::string, value_type_t> &properties) {
while (1) {
auto key = read_string(TYPE_STRING);
auto value = read_value();
mxdebug_if(m_debug, boost::format("%1%Property key: %2%; value read: %3%; value: %4%\n") % level_spacer() % key % value.second % boost::apply_visitor(value_to_string_c(), value.first));
if (key.empty() || !value.second)
break;
properties[key] = value.first;
}
}
std::pair<value_type_t, bool>
script_parser_c::read_value() {
++m_level;
value_type_t value;
auto data_type = static_cast<data_type_e>(m_data.read_uint8());
mxdebug_if(m_debug, boost::format("%1%Data type @ %2%: %3%\n") % level_spacer() % (m_data.getFilePointer() - 1) % static_cast<unsigned int>(data_type));
bool data_read = true;
if ((TYPE_MOVIECLIP == data_type) || (TYPE_NULL == data_type) || (TYPE_UNDEFINED == data_type) || (TYPE_OBJECT_END == data_type))
;
else if (TYPE_REFERENCE == data_type)
m_data.skip(2);
else if (TYPE_DATE == data_type)
m_data.skip(10);
else if (TYPE_NUMBER == data_type)
value = int_to_double(static_cast<int64_t>(m_data.read_uint64_be()));
else if (TYPE_BOOL == data_type)
value = m_data.read_uint8() != 0;
else if ((TYPE_STRING == data_type) || (TYPE_LONG_STRING == data_type)) {
value = read_string(data_type);
m_in_meta_data = boost::get<std::string>(value) == "onMetaData";
} else if (TYPE_OBJECT == data_type) {
std::unordered_map<std::string, value_type_t> dummy;
read_properties(dummy);
} else if (TYPE_ECMAARRAY == data_type) {
m_data.skip(4); // approximate array size
if (m_in_meta_data)
read_properties(m_meta_data);
else {
std::unordered_map<std::string, value_type_t> dummy;
read_properties(dummy);
}
m_in_meta_data = false;
} else if (TYPE_ARRAY == data_type) {
auto num_entries = m_data.read_uint32_be();
for (auto idx = 0u; idx < num_entries; ++idx)
read_value();
} else {
mxdebug_if(m_debug, boost::format("%1%Unknown script data type: %2% position: %3%\n") % level_spacer() % static_cast<unsigned int>(data_type) % (m_data.getFilePointer() - 1));
data_read = false;
}
--m_level;
return std::make_pair(value, data_read);
}
bool
script_parser_c::parse() {
try {
while (m_data.getFilePointer() < static_cast<uint64_t>(m_data.get_size()))
if (!read_value().second)
return false;
} catch (mtx::mm_io::exception &) {
}
return true;
}
}}

103
src/common/amf.h Normal file
View File

@ -0,0 +1,103 @@
/*
mkvmerge -- utility for splicing together matroska files
from component media subtypes
Distributed under the GPL
see the file COPYING for details
or visit http://www.gnu.org/copyleft/gpl.html
helper functions for AMF (Adobe Macromedia Flash) data
Written by Moritz Bunkus <moritz@bunkus.org>.
*/
#ifndef MTX_COMMON_AMF_H
#define MTX_COMMON_AMF_H
#include "common/common_pch.h"
#include <unordered_map>
#include <boost/variant.hpp>
namespace mtx { namespace amf {
typedef boost::variant<double, bool, std::string> value_type_t;
typedef std::unordered_map<std::string, value_type_t> meta_data_t;
class value_to_string_c: public boost::static_visitor<std::string> {
public:
std::string operator ()(double value) const {
return (boost::format("%1%") % value).str();
}
std::string operator ()(bool value) const {
return value ? "yes" : "no";
}
std::string operator ()(std::string const &value) const {
return value;
}
};
class script_parser_c {
public:
typedef enum {
TYPE_NUMBER = 0x00,
TYPE_BOOL = 0x01,
TYPE_STRING = 0x02,
TYPE_OBJECT = 0x03,
TYPE_MOVIECLIP = 0x04,
TYPE_NULL = 0x05,
TYPE_UNDEFINED = 0x06,
TYPE_REFERENCE = 0x07,
TYPE_ECMAARRAY = 0x08,
TYPE_OBJECT_END = 0x09,
TYPE_ARRAY = 0x0a,
TYPE_DATE = 0x0b,
TYPE_LONG_STRING = 0x0c,
} data_type_e;
private:
memory_cptr m_data_mem;
mm_mem_io_c m_data;
meta_data_t m_meta_data;
bool m_in_ecma_array, m_in_meta_data;
unsigned int m_level;
bool m_debug;
public:
script_parser_c(memory_cptr const &mem);
bool parse();
meta_data_t const &get_meta_data() const;
template<typename T>
T const *
get_meta_data_value(std::string const &key) {
auto itr = m_meta_data.find(key);
if (m_meta_data.end() == itr)
return nullptr;
return boost::get<T>(&itr->second);
}
protected:
std::string read_string(data_type_e type);
std::pair<value_type_t, bool> read_value();
void read_properties(std::unordered_map<std::string, value_type_t> &properties);
std::string level_spacer() const;
};
}}
namespace std {
template <>
struct hash<mtx::amf::script_parser_c::data_type_e> {
size_t operator()(mtx::amf::script_parser_c::data_type_e value) const {
return hash<unsigned int>()(static_cast<unsigned int>(value));
}
};
}
#endif // MTX_COMMON_H

View File

@ -85,6 +85,11 @@ public:
trim();
}
void clear() {
if (m_filled)
remove(m_filled);
}
unsigned char *get_buffer() {
return &m_data[m_offset];
}

View File

@ -33,6 +33,7 @@ file_type_t::get_supported() {
#if defined(HAVE_FLAC_FORMAT_H)
s_supported_file_types.push_back(file_type_t(Y("FLAC (Free Lossless Audio Codec)"), "flac ogg"));
#endif
s_supported_file_types.push_back(file_type_t(Y("FLV (Macromedia Flash Video)"), "flv"));
s_supported_file_types.push_back(file_type_t(Y("IVF with VP8 video files"), "ivf"));
s_supported_file_types.push_back(file_type_t(Y("MP4 audio/video files"), "mp4 m4v"));
s_supported_file_types.push_back(file_type_t(Y("MPEG audio files"), "mp2 mp3"));

View File

@ -19,6 +19,8 @@
#include "common/common_pch.h"
#include <unordered_map>
#include "common/bit_cursor.h"
#include "common/byte_buffer.h"
#include "common/checksums.h"
@ -296,6 +298,10 @@ bool
mpeg4::p10::parse_sps(memory_cptr &buffer,
sps_info_t &sps,
bool keep_ar_info) {
std::unordered_map<unsigned int, bool> s_high_level_profile_ids{
{ 44, true }, { 83, true }, { 86, true }, { 100, true }, { 110, true }, { 118, true }, { 122, true }, { 128, true }, { 244, true }
};
int size = buffer->get_size();
unsigned char *newsps = (unsigned char *)safemalloc(size + 100);
memory_cptr mcptr_newsps(new memory_c(newsps, size, true));
@ -318,7 +324,7 @@ mpeg4::p10::parse_sps(memory_cptr &buffer,
sps.profile_compat = w.copy_bits(8, r); // constraints
sps.level_idc = w.copy_bits(8, r); // level_idc
sps.id = gecopy(r, w); // sps id
if (sps.profile_idc >= 100) { // high profile
if (s_high_level_profile_ids[sps.profile_idc]) { // high profile
if ((sps.chroma_format_idc = gecopy(r, w)) == 3) // chroma_format_idc
w.copy_bits(1, r); // separate_colour_plane_flag
gecopy(r, w); // bit_depth_luma_minus8

776
src/input/r_flv.cpp Normal file → Executable file
View File

@ -13,32 +13,770 @@
#include "common/common_pch.h"
#include <avilib.h>
#include "common/amf.h"
#include "common/endian.h"
#include "common/matroska.h"
#include "common/mm_io_x.h"
#include "common/mpeg4_p10.h"
#include "input/r_flv.h"
#include "merge/pr_generic.h"
#include "output/p_aac.h"
#include "output/p_mpeg4_p10.h"
#include "output/p_mp3.h"
#include "output/p_video.h"
#define FLV_DETECT_SIZE 1 * 1024 * 1024
flv_header_t::flv_header_t()
: signature{ 0, 0, 0 }
, version{0}
, type_flags{0}
, data_offset{0}
{
}
bool
flv_header_t::has_video()
const {
return (type_flags & 0x01) == 0x01;
}
bool
flv_header_t::has_audio()
const {
return (type_flags & 0x04) == 0x04;
}
bool
flv_header_t::is_valid()
const {
return std::string(signature, 3) == "FLV";
}
bool
flv_header_t::read(mm_io_cptr const &in) {
return read(in.get());
}
bool
flv_header_t::read(mm_io_c *in) {
if (sizeof(*this) != in->read(this, sizeof(*this)))
return false;
data_offset = get_uint32_be(&data_offset);
return true;
}
// --------------------------------------------------
flv_tag_c::flv_tag_c()
: m_previous_tag_size{}
, m_flags{}
, m_data_size{}
, m_timecode{}
, m_timecode_extended{}
, m_next_position{}
, m_ok{}
, m_debug{debugging_requested("flv_full|flv_tags|flv_tag")}
{
}
bool
flv_tag_c::is_encrypted()
const {
return (m_flags & 0x20) == 0x20;
}
bool
flv_tag_c::is_audio()
const {
return 0x08 == m_flags;
}
bool
flv_tag_c::is_video()
const {
return 0x09 == m_flags;
}
bool
flv_tag_c::is_script_data()
const {
return 0x12 == m_flags;
}
bool
flv_tag_c::read(mm_io_cptr const &in) {
try {
auto position = in->getFilePointer();
m_ok = false;
m_previous_tag_size = in->read_uint32_be();
m_flags = in->read_uint8();
m_data_size = in->read_uint24_be();
m_timecode = in->read_uint24_be();
m_timecode_extended = in->read_uint8();
in->skip(3);
m_next_position = in->getFilePointer() + m_data_size;
m_ok = true;
mxdebug_if(m_debug, boost::format("Tag @ %1%: %2%\n") % position % *this);
return true;
} catch (mtx::mm_io::exception &) {
return false;
}
}
// --------------------------------------------------
flv_track_c::flv_track_c(char type)
: m_type{type}
, m_fourcc{}
, m_headers_read{}
, m_ptzr{-1}
, m_timecode{}
, m_v_version{}
, m_v_width{}
, m_v_height{}
, m_v_dwidth{}
, m_v_dheight{}
, m_v_frame_rate{}
, m_v_aspect_ratio{}
, m_v_cts_offset{}
, m_v_frame_type{}
, m_a_channels{}
, m_a_sample_rate{}
, m_a_bits_per_sample{}
, m_a_profile{}
{
}
bool
flv_track_c::is_audio()
const {
return 'a' == m_type;
}
bool
flv_track_c::is_video()
const {
return 'v' == m_type;
}
bool
flv_track_c::is_valid()
const {
return m_headers_read && !!m_fourcc;
}
bool
flv_track_c::is_ptzr_set()
const {
return -1 != m_ptzr;
}
// --------------------------------------------------
int
flv_reader_c::probe_file(mm_io_c *in,
uint64_t size) {
flv_reader_c::probe_file(mm_io_c *io,
uint64_t) {
try {
if (3 > size)
return 0;
flv_header_t header;
unsigned char buf[3];
in->setFilePointer(0, seek_beginning);
if (in->read(buf, 3) != 3)
return 0;
in->setFilePointer(0, seek_beginning);
if (!memcmp(buf, "FLV", 3)) {
id_result_container_unsupported(in->get_file_name(), "Macromedia Flash Video (FLV)");
// Never reached:
return 1;
}
return 0;
io->setFilePointer(0);
return header.read(io) && header.is_valid() ? 1 : 0;
} catch (...) {
return 0;
}
}
flv_reader_c::flv_reader_c(track_info_c const &ti,
mm_io_cptr const &in)
: generic_reader_c{ti, in}
, m_audio_track_idx{0}
, m_video_track_idx{0}
, m_selected_track_idx{-1}
, m_video_frame_rate{0}
, m_file_done{false}
, m_debug{debugging_requested("flv|flv_full")}
{
}
std::string const
flv_reader_c::get_format_name(bool translate)
const {
return translate ? Y("Macromedia Flash Video") : "Macromedia Flash Video";
}
unsigned int
flv_reader_c::add_track(char type) {
m_tracks.push_back(flv_track_cptr{new flv_track_c(type)});
return m_tracks.size() - 1;
}
void
flv_reader_c::read_headers() {
flv_header_t header;
header.read(m_in);
mxdebug_if(m_debug, boost::format("Header dump: %1%\n") % header);
if (header.has_video())
m_video_track_idx = add_track('v');
if (header.has_audio())
m_audio_track_idx = add_track('a');
bool headers_read = false;
try {
while (!headers_read && !m_file_done && (m_in->getFilePointer() < FLV_DETECT_SIZE)) {
if (process_tag(true)) {
headers_read = true;
for (auto &track : m_tracks)
if (!track->is_valid()) {
headers_read = false;
break;
}
}
if (!m_tag.m_ok)
break;
m_in->setFilePointer(m_tag.m_next_position);
}
} catch (...) {
throw mtx::input::invalid_format_x();
}
m_tracks.erase(std::remove_if(m_tracks.begin(), m_tracks.end(), [](flv_track_cptr &t) { return !t->is_valid(); }),
m_tracks.end());
m_audio_track_idx = -1;
m_video_track_idx = -1;
for (int idx = m_tracks.size(); idx > 0; --idx)
if (m_tracks[idx - 1]->is_video())
m_video_track_idx = idx - 1;
else
m_audio_track_idx = idx - 1;
mxdebug_if(m_debug, boost::format("Detection finished at %1%; headers read? %2%; number valid tracks: %3%\n")
% m_in->getFilePointer() % headers_read % m_tracks.size());
m_in->setFilePointer(9, seek_beginning); // rewind file for later remux
m_file_done = false;
}
flv_reader_c::~flv_reader_c() {
}
void
flv_reader_c::identify() {
id_result_container();
size_t idx = 0;
for (auto track : m_tracks) {
std::vector<std::string> verbose_info;
if (track->m_fourcc == FOURCC('A', 'V', 'C', '1'))
verbose_info.push_back("packetizer:mpeg4_p10_video");
id_result_track(idx, track->is_audio() ? ID_RESULT_TRACK_AUDIO : ID_RESULT_TRACK_VIDEO,
FOURCC('A', 'V', 'C', '1') == track->m_fourcc ? "AVC/h.264"
: FOURCC('F', 'L', 'V', '1') == track->m_fourcc ? "FLV1"
: FOURCC('F', 'L', 'V', '4') == track->m_fourcc ? "FLV4"
: FOURCC('A', 'A', 'C', ' ') == track->m_fourcc ? "AAC"
: FOURCC('M', 'P', '3', ' ') == track->m_fourcc ? "MP3"
: "Unknown",
verbose_info);
++idx;
}
}
void
flv_reader_c::create_packetizer(int64_t id) {
if ((0 > id) || (m_tracks.size() <= static_cast<size_t>(id)) || m_tracks[id]->is_ptzr_set())
return;
auto &track = m_tracks[id];
if (!demuxing_requested(track->m_type, id))
return;
m_ti.m_id = id;
if (track->m_fourcc == FOURCC('A', 'V', 'C', '1'))
create_v_avc_packetizer(track);
else if ( (track->m_fourcc == FOURCC('F', 'L', 'V', '1'))
|| (track->m_fourcc == FOURCC('F', 'L', 'V', '4')))
create_v_generic_packetizer(track);
else if (track->m_fourcc == FOURCC('A', 'A', 'C', ' '))
create_a_aac_packetizer(track);
else if (track->m_fourcc == FOURCC('M', 'P', '3', ' '))
create_a_mp3_packetizer(track);
}
void
flv_reader_c::create_v_avc_packetizer(flv_track_cptr &track) {
if (track->m_private_data) {
m_ti.m_private_data = track->m_private_data->get_buffer();
m_ti.m_private_size = track->m_private_data->get_size();
}
track->m_ptzr = add_packetizer(new mpeg4_p10_video_packetizer_c(this, m_ti, track->m_v_frame_rate, track->m_v_width, track->m_v_height));
show_packetizer_info(m_video_track_idx, PTZR(track->m_ptzr));
m_ti.m_private_data = NULL;
}
void
flv_reader_c::create_v_generic_packetizer(flv_track_cptr &track) {
alBITMAPINFOHEADER bih;
memset(&bih, 0, sizeof(bih));
put_uint32_le(&bih.bi_size, sizeof(bih));
put_uint32_le(&bih.bi_width, track->m_v_width);
put_uint32_le(&bih.bi_height, track->m_v_height);
put_uint16_le(&bih.bi_planes, 1);
put_uint16_le(&bih.bi_bit_count, 24);
put_uint32_le(&bih.bi_compression, get_uint32_be(&track->m_fourcc));
put_uint32_le(&bih.bi_size_image, track->m_v_width * track->m_v_height * 3);
m_ti.m_private_data = reinterpret_cast<unsigned char *>(&bih);
m_ti.m_private_size = sizeof(bih);
track->m_ptzr = add_packetizer(new video_packetizer_c(this, m_ti, MKV_V_MSCOMP, track->m_v_frame_rate, track->m_v_width, track->m_v_height));
show_packetizer_info(m_video_track_idx, PTZR(track->m_ptzr));
m_ti.m_private_data = NULL;
}
void
flv_reader_c::create_a_aac_packetizer(flv_track_cptr &track) {
track->m_ptzr = add_packetizer(new aac_packetizer_c(this, m_ti, AAC_ID_MPEG4, track->m_a_profile, track->m_a_sample_rate, track->m_a_channels, false, true));
show_packetizer_info(m_audio_track_idx, PTZR(track->m_ptzr));
}
void
flv_reader_c::create_a_mp3_packetizer(flv_track_cptr &track) {
track->m_ptzr = add_packetizer(new mp3_packetizer_c(this, m_ti, track->m_a_sample_rate, track->m_a_channels, true));
show_packetizer_info(m_audio_track_idx, PTZR(track->m_ptzr));
}
void
flv_reader_c::create_packetizers() {
for (auto id = 0u; m_tracks.size() > id; ++id)
create_packetizer(id);
}
bool
flv_reader_c::new_stream_v_avc(flv_track_cptr &track,
memory_cptr const &data) {
try {
mm_mem_io_c in(*data.get());
unsigned char buf[6];
if (in.read(buf, 6) != 6)
return false;
auto num_sps = static_cast<unsigned int>(buf[5] & 0x1f);
for (auto sps = 0u; sps < num_sps; sps++) {
auto sps_length = in.read_uint16_be();
auto sps_buf = in.read(sps_length);
mpeg4::p10::sps_info_t sps_info;
try {
if (!mpeg4::p10::parse_sps(sps_buf, sps_info, true))
return false;
} catch (mtx::mm_io::end_of_file_x &) {
}
if (!track->m_v_width)
track->m_v_width = sps_info.width;
if (!track->m_v_height)
track->m_v_height = sps_info.height;
if (!track->m_v_frame_rate && sps_info.num_units_in_tick && sps_info.time_scale)
track->m_v_frame_rate = sps_info.time_scale / sps_info.num_units_in_tick;
}
auto num_pps = in.read_uint8();
for (auto pps = 0u; pps < num_pps; pps++) {
auto pps_length = in.read_uint16_be();
auto pps_buf = in.read(pps_length);
mpeg4::p10::pps_info_t pps_info;
if (!mpeg4::p10::parse_pps(pps_buf, pps_info))
return false;
}
if (!track->m_v_frame_rate)
track->m_v_frame_rate = 25;
} catch (mtx::mm_io::exception &) {
}
mxdebug_if(m_debug, boost::format("new_stream_v_avc: video width: %1%, height: %2%, frame rate: %3%\n") % track->m_v_width % track->m_v_height % track->m_v_frame_rate);
return true;
}
bool
flv_reader_c::process_audio_tag_sound_format(flv_track_cptr &track,
uint8_t sound_format) {
static const std::vector<std::string> s_formats{
"Linear PCM platform endian"
, "ADPCM"
, "MP3"
, "Linear PCM little endian"
, "Nellymoser 16 kHz mono"
, "Nellymoser 8 kHz mono"
, "Nellymoser"
, "G.711 A-law logarithmic PCM"
, "G.711 mu-law logarithmic PCM"
, ""
, "AAC"
, "Speex"
, "MP3 8 kHz"
, "Device-specific sound"
};
if (15 < sound_format) {
mxdebug_if(m_debug, boost::format("Sound format: not handled (%1%)\n") % static_cast<unsigned int>(sound_format));
return true;
}
mxdebug_if(m_debug, boost::format("Sound format: %1%\n") % s_formats[sound_format]);
// AAC
if (10 == sound_format) {
if (!m_tag.m_data_size)
return false;
track->m_fourcc = FOURCC('A', 'A', 'C', ' ');
uint8_t aac_packet_type = m_in->read_uint8();
m_tag.m_data_size--;
if (aac_packet_type != 0) {
// Raw AAC
mxdebug_if(m_debug, boost::format(" AAC sub type: raw\n"));
return true;
}
auto size = std::min<size_t>(m_tag.m_data_size, 5);
if (!size)
return false;
m_tag.m_data_size -= size;
unsigned char specific_codec_buf[5];
if (m_in->read(specific_codec_buf, size) != size)
return false;
int profile, channels, sample_rate, output_sample_rate;
bool sbr;
if (!parse_aac_data(specific_codec_buf, size, profile, channels, sample_rate, output_sample_rate, sbr))
return false;
mxdebug_if(m_debug, boost::format(" AAC sub type: sequence header (profile: %1%, channels: %2%, s_rate: %3%, out_s_rate: %4%, sbr %5%)\n") % profile % channels % sample_rate % output_sample_rate % sbr);
if (sbr)
profile = AAC_PROFILE_SBR;
track->m_a_profile = profile;
track->m_a_channels = channels;
track->m_a_sample_rate = sample_rate;
return true;
}
switch (sound_format) {
case 2: track->m_fourcc = FOURCC('M', 'P', '3', ' '); break;
case 14: track->m_fourcc = FOURCC('M', 'P', '3', ' '); break;
default:
return false;
}
return true;
}
bool
flv_reader_c::process_audio_tag(flv_track_cptr &track) {
uint8_t audiotag_header = m_in->read_uint8();
uint8_t format = (audiotag_header & 0xf0) >> 4;
uint8_t rate = (audiotag_header & 0x0c) >> 2;
uint8_t size = (audiotag_header & 0x02) >> 1;
uint8_t type = audiotag_header & 0x01;
mxdebug_if(m_debug, boost::format("Audio packet found\n"));
if (!m_tag.m_data_size)
return false;
m_tag.m_data_size--;
process_audio_tag_sound_format(track, format);
if (!track->m_a_sample_rate && (4 > rate)) {
static unsigned int s_rates[] = { 5512, 11025, 22050, 44100 };
track->m_a_sample_rate = s_rates[rate];
}
if (!track->m_a_channels)
track->m_a_channels = 0 == type ? 1 : 2;
track->m_headers_read = true;
mxdebug_if(m_debug,
boost::format(" sampling frequency: %1%; sample size: %2%; channels: %3%\n")
% track->m_a_sample_rate % (0 == size ? "8 bits" : "16 bits") % track->m_a_channels);
return true;
}
bool
flv_reader_c::process_video_tag_avc(flv_track_cptr &track) {
if (4 > m_tag.m_data_size)
return false;
track->m_fourcc = FOURCC('A', 'V', 'C', '1');
uint8_t avc_packet_type = m_in->read_uint8();
track->m_v_cts_offset = static_cast<int64_t>(m_in->read_uint24_be());
m_tag.m_data_size -= 4;
// The CTS offset is only valid for NALUs.
if (1 != avc_packet_type)
track->m_v_cts_offset = 0;
// Only sequence headers need more processing
if (0 != avc_packet_type)
return true;
mxdebug_if(m_debug, boost::format(" AVC sequence header at %1%\n") % m_in->getFilePointer());
auto data = m_in->read(m_tag.m_data_size);
m_tag.m_data_size = 0;
if (!track->m_headers_read) {
if (!new_stream_v_avc(track, data))
return false;
track->m_headers_read = true;
}
track->m_extra_data = data;
if (!track->m_private_data)
track->m_private_data = data->clone();
return true;
}
bool
flv_reader_c::process_video_tag_generic(flv_track_cptr &track,
flv_tag_c::codec_type_e codec_id) {
track->m_fourcc = flv_tag_c::CODEC_SORENSON_H263 == codec_id ? FOURCC('F', 'L', 'V', '1')
: flv_tag_c::CODEC_VP6 == codec_id ? FOURCC('F', 'L', 'V', '4')
: FOURCC('B', 'U', 'G', '!');
track->m_headers_read = true;
return true;
}
bool
flv_reader_c::process_video_tag(flv_track_cptr &track) {
static struct {
std::string name;
bool is_key;
} s_frame_types[] = {
{ "key frame", true }
, { "inter frame", false }
, { "disposable inter frame", false }
, { "generated key frame", true }
, { "video info/command frame", false }
};
mxdebug_if(m_debug, boost::format("Video packet found\n"));
if (!m_tag.m_data_size)
return false;
uint8_t video_tag_header = m_in->read_uint8();
m_tag.m_data_size--;
uint8_t frame_type = (video_tag_header >> 4) & 0x0f;
if ((1 <= frame_type) && (5 >= frame_type)) {
auto type = s_frame_types[frame_type - 1];
mxdebug_if(m_debug, boost::format(" Frame type: %1%\n") % type.name);
track->m_v_frame_type = type.is_key ? 'I' : 'P';
} else {
mxdebug_if(m_debug, boost::format(" Frame type unknown (%1%)\n") % static_cast<unsigned int>(frame_type));
track->m_v_frame_type = 'P';
}
static std::string s_codecs[] {
"Sorenson H.263"
, "Screen video"
, "On2 VP6"
, "On2 VP6 with alpha channel"
, "Screen video version 2"
, "H.264"
};
auto codec_id = static_cast<flv_tag_c::codec_type_e>(video_tag_header & 0x0f);
if ((flv_tag_c::CODEC_SORENSON_H263 <= codec_id) && (flv_tag_c::CODEC_H264 >= codec_id)) {
mxdebug_if(m_debug, boost::format(" Codec type: %1%\n") % s_codecs[codec_id - flv_tag_c::CODEC_SORENSON_H263]);
if (flv_tag_c::CODEC_H264 == codec_id)
return process_video_tag_avc(track);
else if ( (flv_tag_c::CODEC_SORENSON_H263 == codec_id)
|| (flv_tag_c::CODEC_VP6 == codec_id)
|| (flv_tag_c::CODEC_VP6_WITH_ALPHA == codec_id))
return process_video_tag_generic(track, codec_id);
} else
mxdebug_if(m_debug, boost::format(" Codec type unknown (%1%)\n") % static_cast<unsigned int>(codec_id));
track->m_headers_read = true;
return true;
}
bool
flv_reader_c::process_script_tag() {
if (!m_tag.m_data_size || (-1 == m_video_track_idx))
return true;
try {
mtx::amf::script_parser_c parser{m_in->read(m_tag.m_data_size)};
parser.parse();
double const *number;
if ((number = parser.get_meta_data_value<double>("framerate"))) {
m_tracks[m_video_track_idx]->m_v_frame_rate = *number;
mxdebug_if(m_debug, boost::format("Video frame rate from meta data: %1%\n") % *number);
}
if ((number = parser.get_meta_data_value<double>("width"))) {
m_tracks[m_video_track_idx]->m_v_width = *number;
mxdebug_if(m_debug, boost::format("Video width from meta data: %1%\n") % *number);
}
if ((number = parser.get_meta_data_value<double>("height"))) {
m_tracks[m_video_track_idx]->m_v_height = *number;
mxdebug_if(m_debug, boost::format("Video height from meta data: %1%\n") % *number);
}
} catch (mtx::mm_io::exception &) {
}
return true;
}
bool
flv_reader_c::process_tag(bool skip_payload) {
m_selected_track_idx = -1;
if (!m_tag.read(m_in)) {
m_file_done = true;
return false;
}
if (m_tag.is_encrypted())
return false;
if (m_tag.is_script_data())
return process_script_tag();
if (m_tag.is_audio())
m_selected_track_idx = m_audio_track_idx;
else if (m_tag.is_video())
m_selected_track_idx = m_video_track_idx;
else
return false;
if ((0 > m_selected_track_idx) || (static_cast<int>(m_tracks.size()) <= m_selected_track_idx))
return false;
auto &track = m_tracks[m_selected_track_idx];
if (m_tag.is_audio() && !process_audio_tag(track))
return false;
else if (m_tag.is_video() && !process_video_tag(track))
return false;
track->m_timecode = m_tag.m_timecode + (m_tag.m_timecode_extended << 24);
mxdebug_if(m_debug, boost::format("Data size after processing: %1%; timecode in ms: %2%\n") % m_tag.m_data_size % track->m_timecode);
if (!m_tag.m_data_size || skip_payload)
return true;
track->m_payload = m_in->read(m_tag.m_data_size);
return true;
}
file_status_e
flv_reader_c::read(generic_packetizer_c *,
bool) {
if (m_file_done || m_in->eof())
return flush_packetizers();
bool tag_processed = false;
try {
tag_processed = process_tag();
} catch (mtx::mm_io::exception &ex) {
mxdebug_if(m_debug, boost::format("Exception: %1%\n") % ex);
m_tag.m_ok = false;
}
if (!tag_processed) {
if (m_tag.m_ok && m_in->setFilePointer2(m_tag.m_next_position))
return FILE_STATUS_MOREDATA;
m_file_done = true;
return flush_packetizers();
}
if (-1 == m_selected_track_idx)
return FILE_STATUS_MOREDATA;
auto &track = m_tracks[m_selected_track_idx];
if (!track->m_payload)
return FILE_STATUS_MOREDATA;
if (-1 != track->m_ptzr) {
track->m_timecode = (track->m_timecode + track->m_v_cts_offset) * 1000000ll;
mxdebug_if(m_debug, boost::format(" PTS in nanoseconds: %1%\n") % track->m_timecode);
int64_t duration = -1;
if (track->m_v_frame_rate && (track->m_fourcc == FOURCC('A', 'V', 'C', '1')))
duration = 1000000000ll / track->m_v_frame_rate;
auto packet = new packet_t(track->m_payload, track->m_timecode, duration, 'I' == track->m_v_frame_type ? VFT_IFRAME : VFT_PFRAMEAUTOMATIC, VFT_NOBFRAME);
if (track->m_extra_data)
packet->codec_state = track->m_extra_data;
PTZR(track->m_ptzr)->process(packet);
}
track->m_payload.reset();
track->m_extra_data.reset();
if (m_in->setFilePointer2(m_tag.m_next_position))
return FILE_STATUS_MOREDATA;
m_file_done = true;
return flush_packetizers();
}

151
src/input/r_flv.h Normal file → Executable file
View File

@ -16,9 +16,156 @@
#include "common/common_pch.h"
class flv_reader_c {
#include <ostream>
#include "common/byte_buffer.h"
#include "common/mm_io.h"
#include "merge/pr_generic.h"
#if defined(COMP_MSC)
#pragma pack(push,1)
#endif
struct PACKED_STRUCTURE flv_header_t {
char signature[3];
uint8_t version, type_flags;
uint32_t data_offset;
flv_header_t();
bool read(mm_io_c *in);
bool read(mm_io_cptr const &in);
bool has_video() const;
bool has_audio() const;
bool is_valid() const;
};
inline std::ostream &
operator <<(std::ostream &out,
flv_header_t const &h) {
// "Cannot bind packed field to unsigned int &" if "data_offset" is used directly.
auto local_data_offset = h.data_offset;
out << (boost::format("[file version: %1% data offset: %2% video track present: %3% audio track present: %4%]")
% static_cast<unsigned int>(h.version) % local_data_offset % h.has_video() % h.has_audio()).str();
return out;
}
#if defined(COMP_MSC)
#pragma pack(pop)
#endif
class flv_tag_c {
public:
static int probe_file(mm_io_c *in, uint64_t size);
typedef enum {
CODEC_SORENSON_H263 = 2
, CODEC_SCREEN_VIDEO
, CODEC_VP6
, CODEC_VP6_WITH_ALPHA
, CODEC_SCREEN_VIDEO_V2
, CODEC_H264
} codec_type_e;
public:
uint32_t m_previous_tag_size;
uint8_t m_flags;
uint64_t m_data_size, m_timecode, m_timecode_extended, m_next_position;
bool m_ok, m_debug;
public:
flv_tag_c();
bool read(const mm_io_cptr &in);
bool is_encrypted() const;
bool is_audio() const;
bool is_video() const;
bool is_script_data() const;
};
inline std::ostream &
operator <<(std::ostream &out,
flv_tag_c const &t) {
out << (boost::format("[prev size: %1% flags: %2% data size: %3% timecode+ex: %4%/%5% next pos: %6% ok: %7%]")
% t.m_previous_tag_size % static_cast<unsigned int>(t.m_flags) % t.m_data_size % t.m_timecode % t.m_timecode_extended % t.m_next_position % t.m_ok).str();
return out;
}
class flv_track_c {
public:
char m_type; // 'v' for video, 'a' for audio
uint32_t m_fourcc;
bool m_headers_read;
memory_cptr m_payload, m_private_data, m_extra_data;
int m_ptzr; // the actual packetizer instance
int64_t m_timecode;
// video related parameters
unsigned int m_v_version, m_v_width, m_v_height, m_v_dwidth, m_v_dheight;
double m_v_frame_rate, m_v_aspect_ratio;
int64_t m_v_cts_offset;
char m_v_frame_type;
// audio related parameters
unsigned int m_a_channels, m_a_sample_rate, m_a_bits_per_sample, m_a_profile;
flv_track_c(char type);
bool is_audio() const;
bool is_video() const;
bool is_valid() const;
bool is_ptzr_set() const;
};
typedef std::shared_ptr<flv_track_c> flv_track_cptr;
class flv_reader_c: public generic_reader_c {
private:
int m_audio_track_idx, m_video_track_idx, m_selected_track_idx;
double m_video_frame_rate;
flv_tag_c m_tag;
bool m_file_done;
std::vector<flv_track_cptr> m_tracks;
bool m_debug;
public:
flv_reader_c(const track_info_c &ti, const mm_io_cptr &in);
virtual ~flv_reader_c();
virtual bool new_stream_v_avc(flv_track_cptr &track, memory_cptr const &data);
virtual void read_headers();
virtual file_status_e read(generic_packetizer_c *ptzr, bool force = false);
virtual void identify();
virtual void create_packetizer(int64_t tid);
virtual void create_packetizers();
static int probe_file(mm_io_c *io, uint64_t size);
virtual std::string const get_format_name(bool translate = true) const;
protected:
bool process_tag(bool skip_payload = false);
bool process_script_tag();
bool process_audio_tag(flv_track_cptr &track);
bool process_audio_tag_sound_format(flv_track_cptr &track, uint8_t sound_format);
bool process_video_tag(flv_track_cptr &track);
bool process_video_tag_avc(flv_track_cptr &track);
bool process_video_tag_generic(flv_track_cptr &track, flv_tag_c::codec_type_e codec_id);
void create_a_aac_packetizer(flv_track_cptr &track);
void create_a_mp3_packetizer(flv_track_cptr &track);
void create_v_avc_packetizer(flv_track_cptr &track);
void create_v_generic_packetizer(flv_track_cptr &track);
unsigned int add_track(char type);
};
#endif // MTX_R_FLV_H

View File

@ -1204,6 +1204,9 @@ create_readers() {
file.reader = new flac_reader_c(*file.ti, input_file);
break;
#endif
case FILE_TYPE_FLV:
file.reader = new flv_reader_c(*file.ti, input_file);
break;
case FILE_TYPE_IVF:
file.reader = new ivf_reader_c(*file.ti, input_file);
break;

View File

@ -224,3 +224,4 @@ T_375keep_pcm_timecodes:07b82348a0a4898792b578620c60d91c:passed:20121208-154352:
T_376append_empty_tracks:bb24fbaeed4803db5c3fb5cf86c8f11f-bdee9f14cf713a27ca6f7295215fce57-73604907be7a3b196a1bbe9cc0af11f1:passed:20121208-190510:1.098954043
T_377mp3_skip_id3_properly:dfef05c8596cafafeb90de5bfa86b54c:passed:20121216-212310:0.042343772
T_378deprecated_iso_639_2_codes:hrv+rum+srp-0270a2ad78e5881e406a770709e8393d:passed:20121217-171624:0.066671232
T_379flv:21f4b2199ccd7f55e0e917420f2a2ffb-8c53cc6b896ff2f158c9273d7f453e91-c7435e7e57cb8c63726a4ce1e6a9bddd-8c53cc6b896ff2f158c9273d7f453e91-974aafdd68451f22132d779d49fab69b-e3cdbe4c0713d03fd9e50e56b35ba3af-0d5e4943e929fc6934c7df3beca14099-e3cdbe4c0713d03fd9e50e56b35ba3af-320826707f7cd737569a91e6dcd5be70-c230b5cfb9693c1d99b3c7e16c630071-efbe31f64bccfbaa68f345578a2dca21-c230b5cfb9693c1d99b3c7e16c630071:passed:20121223-134722:2.484513374

11
tests/test-379flv.rb Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/ruby -w
# T_379flv
describe "mkvmerge / Macromedia Flash Video files (FLV)"
%w{flv1-mp3-400-240 flv1-mp3-2 h264-aac-640-360}.each do |file_name|
file_name = "data/flv/#{file_name}.flv"
['', '-A', '-D'].each { |args| test_merge file_name, :args => args }
test_merge file_name, :args => "-a 2", :exit_code => :warning
end