Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

obs-outputs: Add Hybrid MOV support #11679

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UI/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ Basic.Settings.Output.Format.MOV="QuickTime (.mov)"
Basic.Settings.Output.Format.TS="MPEG-TS (.ts)"
Basic.Settings.Output.Format.HLS="HLS (.m3u8 + .ts)"
Basic.Settings.Output.Format.hMP4="Hybrid MP4 [BETA] (.mp4)"
Basic.Settings.Output.Format.hMOV="Hybrid MOV [BETA] (.mov)"
Basic.Settings.Output.Format.fMP4="Fragmented MP4 (.mp4)"
Basic.Settings.Output.Format.fMOV="Fragmented MOV (.mov)"
Basic.Settings.Output.Format.TT.fragmented_mov="Fragmented MOV writes the recording in chunks and does not require the same finalization as traditional MOV files.\nThis ensures the file remains playable even if writing to disk is interrupted, for example, as a result of a BSOD or power loss.\n\nThis may not be compatible with all players and editors. Use File → Remux Recordings to convert the file into a more compatible format if necessary."
Expand Down
12 changes: 12 additions & 0 deletions UI/ffmpeg-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ static const unordered_map<string, unordered_set<string>> codec_compat = {
"pcm_s24le",
"pcm_f32le",
}},
// Not part of FFmpeg, see obs-outputs module
{"hybrid_mov",
{
"h264",
"hevc",
"prores",
"aac",
"alac",
"pcm_s16le",
"pcm_s24le",
"pcm_f32le",
}},
{"mov",
{
"h264",
Expand Down
4 changes: 3 additions & 1 deletion UI/obs-app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1719,8 +1719,10 @@ string GetFormatExt(const char *container)
string ext = container;
if (ext == "fragmented_mp4")
ext = "mp4";
if (ext == "hybrid_mp4")
else if (ext == "hybrid_mp4")
ext = "mp4";
else if (ext == "hybrid_mov")
ext = "mov";
else if (ext == "fragmented_mov")
ext = "mov";
else if (ext == "hls")
Expand Down
20 changes: 14 additions & 6 deletions UI/window-basic-main-outputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,13 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this);
}

bool use_native = strcmp(recFormat, "hybrid_mp4") == 0;
fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output",
nullptr, nullptr);
const char *mux = "ffmper_muxer";
if (strcmp(recFormat, "hybrid_mp4") == 0)
mux = "mp4_output";
else if (strcmp(recFormat, "hybrid_mov") == 0)
mux = "mov_output";

fileOutput = obs_output_create(mux, "simple_file_output ", nullptr, nullptr);
if (!fileOutput)
throw "Failed to create recording output "
"(simple output)";
Expand Down Expand Up @@ -1546,9 +1550,13 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this);
}

bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0;
fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr,
nullptr);
const char *mux = "ffmper_muxer";
if (strcmp(recFormat, "hybrid_mp4") == 0)
mux = "mp4_output";
else if (strcmp(recFormat, "hybrid_mov") == 0)
mux = "mov_output";

fileOutput = obs_output_create(mux, "adv_file_output", nullptr, nullptr);
if (!fileOutput)
throw "Failed to create recording output "
"(advanced output)";
Expand Down
10 changes: 8 additions & 2 deletions UI/window-basic-main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1578,13 +1578,19 @@ static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6),

extern void CheckExistingCookieId();

#ifdef __APPLE__
#ifdef __APPLE__ // macOS
#if OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
#define DEFAULT_CONTAINER "fragmented_mov"
#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
#else
#define DEFAULT_CONTAINER "hybrid_mov"
#endif
#else // Windows/Linux
#if OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
#define DEFAULT_CONTAINER "mkv"
#else
#define DEFAULT_CONTAINER "hybrid_mp4"
#endif
#endif

bool OBSBasic::InitBasicConfigDefaults()
{
Expand Down
2 changes: 2 additions & 0 deletions UI/window-basic-settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ void OBSBasicSettings::LoadFormats()
ui->simpleOutRecFormat->addItem(FORMAT_STR("MP4"), "mp4");
ui->simpleOutRecFormat->addItem(FORMAT_STR("MOV"), "mov");
ui->simpleOutRecFormat->addItem(FORMAT_STR("hMP4"), "hybrid_mp4");
ui->simpleOutRecFormat->addItem(FORMAT_STR("hMOV"), "hybrid_mov");
ui->simpleOutRecFormat->addItem(FORMAT_STR("fMP4"), "fragmented_mp4");
ui->simpleOutRecFormat->addItem(FORMAT_STR("fMOV"), "fragmented_mov");
ui->simpleOutRecFormat->addItem(FORMAT_STR("TS"), "mpegts");
Expand All @@ -1085,6 +1086,7 @@ void OBSBasicSettings::LoadFormats()
ui->advOutRecFormat->addItem(FORMAT_STR("MP4"), "mp4");
ui->advOutRecFormat->addItem(FORMAT_STR("MOV"), "mov");
ui->advOutRecFormat->addItem(FORMAT_STR("hMP4"), "hybrid_mp4");
ui->advOutRecFormat->addItem(FORMAT_STR("hMOV"), "hybrid_mov");
ui->advOutRecFormat->addItem(FORMAT_STR("fMP4"), "fragmented_mp4");
ui->advOutRecFormat->addItem(FORMAT_STR("fMOV"), "fragmented_mov");
ui->advOutRecFormat->addItem(FORMAT_STR("TS"), "mpegts");
Expand Down
1 change: 1 addition & 0 deletions plugins/obs-outputs/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ MP4Output="MP4 File Output"
MP4Output.FilePath="File Path"
MP4Output.StartChapter="Start"
MP4Output.UnnamedChapter="Unnamed"
MOVOutput="MOV File Output"

IPFamily="IP Address Family"
IPFamily.Both="IPv4 and IPv6 (Default)"
Expand Down
92 changes: 83 additions & 9 deletions plugins/obs-outputs/mp4-mux-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@
#include <util/deque.h>
#include <util/serializer.h>

/* Flavour for target compatibility */
enum mp4_flavour {
MP4, /* ISO/IEC 14496-12 */
MOV, /* Apple QuickTime */
CMAF, /* ISO/IEC 23000-19 */
};

enum mp4_track_type {
TRACK_UNKNOWN,
TRACK_VIDEO,
Expand All @@ -44,6 +37,7 @@ enum mp4_codec {
CODEC_H264,
CODEC_HEVC,
CODEC_AV1,
CODEC_PRORES,

/* Audio Codecs */
CODEC_AAC,
Expand Down Expand Up @@ -97,7 +91,7 @@ struct mp4_track {
/* Time Base (1/FPS for video, 1/sample rate for audio) */
uint32_t timebase_num;
uint32_t timebase_den;
/* Output timescale calculated from time base (Video only) */
/* Output timescale calculated from time base */
uint32_t timescale;

/* First PTS this track has seen (in track timescale) */
Expand Down Expand Up @@ -133,7 +127,7 @@ struct mp4_mux {
struct serializer *serializer;

/* Target format compatibility */
enum mp4_flavour mode;
enum mp4_flavour flavour;

/* Flags */
enum mp4_mux_flags flags;
Expand Down Expand Up @@ -340,3 +334,83 @@ static const char CHAPTER_PKT_FOOTER[12] = {
0x00, 0x00, 0x01, 0x00
};
/* clang-format on */

/** QTFF/MOV specifics **/

/* https://developer.apple.com/documentation/quicktime-file-format/sound_sample_description_version_2#LPCM-flag-values */
enum lpcm_flags {
kAudioFormatFlagIsFloat = (1 << 0),
kAudioFormatFlagIsSignedInteger = (1 << 2),
kAudioFormatFlagIsPacked = (1 << 3),
kLinearPCMFormatFlagIsFloat = kAudioFormatFlagIsFloat,
kLinearPCMFormatFlagIsSignedInteger = kAudioFormatFlagIsSignedInteger,
kLinearPCMFormatFlagIsPacked = kAudioFormatFlagIsPacked,
};

static inline uint32_t get_lpcm_flags(enum mp4_codec codec)
{
if (codec == CODEC_PCM_F32)
return kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsPacked;
if (codec == CODEC_PCM_I16 || codec == CODEC_PCM_I24)
return kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;

return 0;
}

enum channel_map_bits {
FL = 1 << 0,
FR = 1 << 1,
FC = 1 << 2,
LFE = 1 << 3,
RL = 1 << 4,
RR = 1 << 5,
RC = 1 << 8,
SL = 1 << 9,
SR = 1 << 10,
};

static uint32_t get_mov_channel_bitmap(enum speaker_layout layout)
{
switch (layout) {
case SPEAKERS_MONO:
return FC;
case SPEAKERS_STEREO:
return FL | FR;
case SPEAKERS_2POINT1:
return FL | FR | LFE;
case SPEAKERS_4POINT0:
return FL | FR | FC | RC;
case SPEAKERS_4POINT1:
return FL | FR | FC | LFE | RC;
case SPEAKERS_5POINT1:
return FL | FR | FC | LFE | RL | RR;
case SPEAKERS_7POINT1:
return FL | FR | FC | LFE | RL | RR | SL | SR;
case SPEAKERS_UNKNOWN:
break;
}

return 0;
}

enum coreaudio_layout {
kAudioChannelLayoutTag_UseChannelBitmap = (1 << 16) | 0,
kAudioChannelLayoutTag_Mono = (100 << 16) | 1,
kAudioChannelLayoutTag_Stereo = (101 << 16) | 2,
kAudioChannelLayoutTag_DVD_4 = (133 << 16) | 3, // 2.1 (AAC Only)
};

static enum coreaudio_layout get_mov_channel_layout(enum mp4_codec codec, enum speaker_layout layout)
{
switch (layout) {
case SPEAKERS_MONO:
return kAudioChannelLayoutTag_Mono;
case SPEAKERS_STEREO:
return kAudioChannelLayoutTag_Stereo;
case SPEAKERS_2POINT1:
/* Only supported for AAC. */
return codec == CODEC_AAC ? kAudioChannelLayoutTag_DVD_4 : kAudioChannelLayoutTag_UseChannelBitmap;
default:
return kAudioChannelLayoutTag_UseChannelBitmap;
}
}
Loading
Loading