Skip to content

Commit 98b7ab2

Browse files
wysaidclaude
andcommitted
fix(writer): align auto bitrate with YouTube official recommended settings
Replace the simplistic pixels*4 formula with YouTube's official H.264 bitrate recommendations (support.google.com/youtube/answer/2853702). Uses linear interpolation by pixel count between reference points (720p, 1080p, 1440p, 4K), extrapolation above 4K, and 720p floor for lower resolutions. HEVC targets ~60% of H.264 bitrate. Also: - Change default codec to H264 (better compatibility and performance) - Change default bitRate to 0 (auto) so YouTube-based auto takes effect - Remove unreachable null check on getFreeFrame() and document the non-null guarantee on its declaration Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5a0d820 commit 98b7ab2

6 files changed

Lines changed: 97 additions & 12 deletions

File tree

examples/desktop/6-record_video_c.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,12 @@ int main(int argc, char** argv) {
126126
// Configure and open video writer
127127
CcapWriterConfig writerConfig;
128128
memset(&writerConfig, 0, sizeof(writerConfig));
129-
writerConfig.codec = CCAP_VIDEO_CODEC_HEVC;
129+
writerConfig.codec = CCAP_VIDEO_CODEC_H264;
130130
writerConfig.container = CCAP_VIDEO_FORMAT_MP4;
131131
writerConfig.width = (uint32_t)realWidth;
132132
writerConfig.height = (uint32_t)realHeight;
133133
writerConfig.frameRate = realFps > 0.0 ? realFps : 30.0;
134-
writerConfig.bitRate = 5000000;
134+
writerConfig.bitRate = 0; // auto bit rate based on resolution and codec (YouTube recommended)
135135

136136
CcapVideoWriter* writer = ccap_video_writer_create();
137137
if (!writer) {

include/ccap_writer.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ namespace ccap {
2727
* @brief Video codec for encoding.
2828
*/
2929
enum class VideoCodec {
30-
HEVC, ///< H.265 / HEVC (preferred, better compression)
31-
H264, ///< H.264 / AVC (fallback, wider compatibility)
30+
H264, ///< H.264 / AVC (default, best compatibility and performance)
31+
HEVC, ///< H.265 / HEVC (better compression, less compatible)
3232
};
3333

3434
/**
@@ -43,12 +43,12 @@ enum class VideoFormat {
4343
* @brief Configuration for video writer.
4444
*/
4545
struct WriterConfig {
46-
VideoCodec codec = VideoCodec::HEVC; ///< Preferred codec; auto-fallback to H.264 if unavailable
46+
VideoCodec codec = VideoCodec::H264; ///< Default codec; auto-fallback to HEVC if H.264 is unavailable
4747
VideoFormat container = VideoFormat::MP4;
4848
uint32_t width = 0; ///< Frame width in pixels
4949
uint32_t height = 0; ///< Frame height in pixels
5050
double frameRate = 30.0; ///< Target frame rate (default 30fps; used for timestamp generation when timestampNs is 0)
51-
uint64_t bitRate = 5'000'000; ///< Target bit rate in bits/s; 0 = auto
51+
uint64_t bitRate = 0; ///< Target bit rate in bits/s; 0 = auto (YouTube official recommended bitrates)
5252
};
5353

5454
/**

include/ccap_writer_c.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ typedef struct CcapVideoWriter CcapVideoWriter;
2727

2828
/** @brief Video codec enumeration */
2929
typedef enum {
30-
CCAP_VIDEO_CODEC_HEVC = 0, ///< H.265 / HEVC (preferred)
31-
CCAP_VIDEO_CODEC_H264 = 1, ///< H.264 / AVC (fallback)
30+
CCAP_VIDEO_CODEC_H264 = 0, ///< H.264 / AVC (default, best compatibility)
31+
CCAP_VIDEO_CODEC_HEVC = 1, ///< H.265 / HEVC (better compression, less compatible)
3232
} CcapVideoCodec;
3333

3434
/** @brief Video container format */
@@ -50,14 +50,14 @@ typedef struct {
5050
uint32_t width; ///< Frame width
5151
uint32_t height; ///< Frame height
5252
double frameRate; ///< Target frame rate; 0 lets open() normalize to 30fps
53-
uint64_t bitRate; ///< Target bit rate in bits/s (0 = auto)
53+
uint64_t bitRate; ///< Target bit rate in bits/s (0 = auto, YouTube recommended bitrates)
5454
} CcapWriterConfig;
5555

5656
/**
5757
* @brief Default initializer for `CcapWriterConfig`.
5858
* @note `width` and `height` remain 0 and must be assigned by the caller.
5959
*/
60-
#define CCAP_WRITER_CONFIG_INIT { CCAP_VIDEO_CODEC_HEVC, CCAP_VIDEO_FORMAT_MP4, 0u, 0u, 30.0, 5000000ULL }
60+
#define CCAP_WRITER_CONFIG_INIT { CCAP_VIDEO_CODEC_H264, CCAP_VIDEO_FORMAT_MP4, 0u, 0u, 30.0, 0ULL }
6161

6262
/* ========== Writer Lifecycle ========== */
6363

src/ccap_writer_apple.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ bool open(std::string_view filePath, const WriterConfig& config) override {
7575
bool tryOpen(AVFileType fileType, NSString* pathStr, AVVideoCodecType codec) {
7676
NSURL* url = [NSURL fileURLWithPath: pathStr];
7777
NSError* error = nil;
78-
int64_t bitRate = (m_config.bitRate > 0) ? static_cast<int64_t>(m_config.bitRate) : static_cast<int64_t>(m_config.width) * m_config.height * 4;
78+
int64_t bitRate = static_cast<int64_t>(effectiveBitRate(m_config));
7979
int frameRateInt = (m_config.frameRate > 0) ? static_cast<int>(m_config.frameRate) : 30;
8080
int maxKeyFrameInterval = frameRateInt * 2;
8181

src/ccap_writer_imp.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,91 @@ struct VideoWriter::Impl {
3737
/// Returns nullptr on unsupported platforms.
3838
VideoWriter::Impl* createVideoWriterImpl();
3939

40+
/// Compute auto bit rate based on resolution and frame rate.
41+
///
42+
/// Reference: YouTube official recommended bitrates for H.264 encoding.
43+
/// https://support.google.com/youtube/answer/2853702
44+
///
45+
/// YouTube H.264 reference points (Mbps):
46+
/// 720p @30fps → 7.5 720p @60fps → 9.0
47+
/// 1080p @30fps → 10 1080p @60fps → 12
48+
/// 1440p @30fps → 15 1440p @60fps → 24
49+
/// 2160p @30fps → 30 2160p @60fps → 35
50+
///
51+
/// For resolutions below 720p, the 720p rate is used as floor.
52+
/// For resolutions between reference points, bit rate is linearly interpolated by pixel count.
53+
/// For resolutions above 4K, bit rate is extrapolated by pixel count.
54+
/// For HEVC, bit rate is scaled down to ~60% of H.264 (HEVC achieves similar quality at lower bit rate).
55+
inline uint64_t computeAutoBitRate(uint32_t width, uint32_t height, double frameRate, VideoCodec codec) {
56+
const double kMbps = 1'000'000.0;
57+
const double fps = (frameRate > 0.0) ? frameRate : 30.0;
58+
const bool is60fps = fps > 45.0;
59+
60+
// YouTube H.264 reference data points: (pixelCount, bitrateInMbps)
61+
struct RefPoint { double pixels; double bitrateMbps; };
62+
static const RefPoint refs30[] = {
63+
{1280 * 720, 7.5},
64+
{1920 * 1080, 10.0},
65+
{2560 * 1440, 15.0},
66+
{3840 * 2160, 30.0},
67+
};
68+
static const RefPoint refs60[] = {
69+
{1280 * 720, 9.0},
70+
{1920 * 1080, 12.0},
71+
{2560 * 1440, 24.0},
72+
{3840 * 2160, 35.0},
73+
};
74+
75+
const RefPoint* refs = is60fps ? refs60 : refs30;
76+
const int refCount = 4;
77+
const double pixels = static_cast<double>(width) * height;
78+
double bitrateMbps;
79+
80+
if (pixels <= refs[0].pixels) {
81+
// Below 720p: use 720p floor
82+
bitrateMbps = refs[0].bitrateMbps;
83+
} else if (pixels >= refs[refCount - 1].pixels) {
84+
// Above 4K: extrapolate using slope of the last two reference points
85+
const auto& a = refs[refCount - 2];
86+
const auto& b = refs[refCount - 1];
87+
double slope = (b.bitrateMbps - a.bitrateMbps) / (b.pixels - a.pixels);
88+
bitrateMbps = b.bitrateMbps + slope * (pixels - b.pixels);
89+
} else {
90+
// Between reference points: linear interpolation by pixel count
91+
int i = 0;
92+
while (i < refCount - 1 && pixels > refs[i + 1].pixels) i++;
93+
const auto& lo = refs[i];
94+
const auto& hi = refs[i + 1];
95+
double t = (pixels - lo.pixels) / (hi.pixels - lo.pixels);
96+
bitrateMbps = lo.bitrateMbps + t * (hi.bitrateMbps - lo.bitrateMbps);
97+
}
98+
99+
// Scale for non-standard frame rates between 30 and 60
100+
if (!is60fps && fps > 30.0) {
101+
const auto& r30 = refs30;
102+
const auto& r60 = refs60;
103+
// Average ratio across reference points
104+
double ratio = 0;
105+
for (int i = 0; i < refCount; i++) ratio += r60[i].bitrateMbps / r30[i].bitrateMbps;
106+
ratio /= refCount; // ~1.27
107+
double t = (fps - 30.0) / 30.0;
108+
bitrateMbps *= (1.0 + t * (ratio - 1.0));
109+
}
110+
111+
// HEVC achieves similar visual quality at ~60% of H.264 bit rate
112+
if (codec == VideoCodec::HEVC) {
113+
bitrateMbps *= 0.6;
114+
}
115+
116+
return static_cast<uint64_t>(bitrateMbps * kMbps);
117+
}
118+
119+
/// Resolve effective bit rate: use user value if set, otherwise compute auto.
120+
inline uint64_t effectiveBitRate(const WriterConfig& config) {
121+
if (config.bitRate > 0) return config.bitRate;
122+
return computeAutoBitRate(config.width, config.height, config.frameRate, config.codec);
123+
}
124+
40125
// ---- Shared NV12 conversion helpers (used by both platform implementations) ----
41126

42127
inline int orientedRowIndex(FrameOrientation orientation, int row, int height) {

src/ccap_writer_windows.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class WriterWindows : public VideoWriter::Impl {
255255

256256
pOutputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
257257
pOutputType->SetGUID(MF_MT_SUBTYPE, videoCodec);
258-
pOutputType->SetUINT32(MF_MT_AVG_BITRATE, static_cast<UINT32>(config.bitRate > 0 ? config.bitRate : config.width * config.height * 4));
258+
pOutputType->SetUINT32(MF_MT_AVG_BITRATE, static_cast<UINT32>(effectiveBitRate(config)));
259259
pOutputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
260260
MFSetAttributeSize(pOutputType, MF_MT_FRAME_SIZE, config.width, config.height);
261261

0 commit comments

Comments
 (0)