mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2025-02-26 08:22:31 +00:00
parent
b57b7f8f71
commit
8376df5900
@ -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
147
src/common/amf.cpp
Normal 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
103
src/common/amf.h
Normal 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
|
@ -85,6 +85,11 @@ public:
|
||||
trim();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (m_filled)
|
||||
remove(m_filled);
|
||||
}
|
||||
|
||||
unsigned char *get_buffer() {
|
||||
return &m_data[m_offset];
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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
776
src/input/r_flv.cpp
Normal file → Executable 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
151
src/input/r_flv.h
Normal file → Executable 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
|
||||
|
@ -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;
|
||||
|
@ -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
11
tests/test-379flv.rb
Normal 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
|
Loading…
Reference in New Issue
Block a user