Skip to content

Commit

Permalink
SDP の profile-level-id を見るようにする
Browse files Browse the repository at this point in the history
  • Loading branch information
melpon committed Apr 11, 2024
1 parent 76fee35 commit c4d00e2
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ target_sources(sorac
src/current_time.cpp
src/data_channel.cpp
src/default_encoder_adapter.cpp
src/h264_profile_level_id.cpp
src/open_h264_video_encoder.cpp
src/opus_audio_encoder.cpp
src/signaling.cpp
Expand All @@ -118,6 +119,8 @@ target_sources(sorac
include/sorac/bitrate.hpp
include/sorac/current_time.hpp
include/sorac/data_channel.hpp
include/sorac/default_encoder_adapter.hpp
include/sorac/h264_profile_level_id.hpp
include/sorac/open_h264_video_encoder.hpp
include/sorac/opus_audio_encoder.hpp
include/sorac/signaling.hpp
Expand Down
36 changes: 36 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,39 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
```

## WebRTC

https://webrtc.googlesource.com/src/

```
Copyright (c) 2011, The WebRTC project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
67 changes: 67 additions & 0 deletions include/sorac/h264_profile_level_id.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/video_codecs/h264_profile_level_id.h
// から必要な部分だけ抜き出して修正したもの。

/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/

#ifndef API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_
#define API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_

#include <optional>
#include <string>

namespace sorac {

enum class H264Profile {
kProfileConstrainedBaseline,
kProfileBaseline,
kProfileMain,
kProfileConstrainedHigh,
kProfileHigh,
kProfilePredictiveHigh444,
};

// All values are equal to ten times the level number, except level 1b which is
// special.
enum class H264Level {
kLevel1_b = 0,
kLevel1 = 10,
kLevel1_1 = 11,
kLevel1_2 = 12,
kLevel1_3 = 13,
kLevel2 = 20,
kLevel2_1 = 21,
kLevel2_2 = 22,
kLevel3 = 30,
kLevel3_1 = 31,
kLevel3_2 = 32,
kLevel4 = 40,
kLevel4_1 = 41,
kLevel4_2 = 42,
kLevel5 = 50,
kLevel5_1 = 51,
kLevel5_2 = 52
};

struct H264ProfileLevelId {
H264ProfileLevelId(H264Profile profile, H264Level level)
: profile(profile), level(level) {}
H264Profile profile;
H264Level level;
};

// Parse profile level id that is represented as a string of 3 hex bytes.
// Nothing will be returned if the string is not a recognized H264
// profile level id.
std::optional<H264ProfileLevelId> ParseH264ProfileLevelId(const char* str);

} // namespace sorac

#endif // API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_
5 changes: 4 additions & 1 deletion include/sorac/vt_h26x_video_encoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#define SORAC_VT_H26X_VIDEO_ENCODER_HPP_

#include <memory>
#include <optional>
#include <string>

#include "h264_profile_level_id.hpp"
#include "video_encoder.hpp"

namespace sorac {
Expand All @@ -14,7 +16,8 @@ enum class VTH26xVideoEncoderType {
};

std::shared_ptr<VideoEncoder> CreateVTH26xVideoEncoder(
VTH26xVideoEncoderType type);
VTH26xVideoEncoderType type,
std::optional<H264ProfileLevelId> profile);

} // namespace sorac

Expand Down
133 changes: 133 additions & 0 deletions src/h264_profile_level_id.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/video_codecs/h264_profile_level_id.cpp
// から必要な部分だけ抜き出して修正したもの。

/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/

#include "sorac/h264_profile_level_id.hpp"

#include <cstdio>
#include <cstdlib>
#include <string>

namespace sorac {

namespace {

// For level_idc=11 and profile_idc=0x42, 0x4D, or 0x58, the constraint set3
// flag specifies if level 1b or level 1.1 is used.
const uint8_t kConstraintSet3Flag = 0x10;

// Convert a string of 8 characters into a byte where the positions containing
// character c will have their bit set. For example, c = 'x', str = "x1xx0000"
// will return 0b10110000. constexpr is used so that the pattern table in
// kProfilePatterns is statically initialized.
constexpr uint8_t ByteMaskString(char c, const char (&str)[9]) {
return (str[0] == c) << 7 | (str[1] == c) << 6 | (str[2] == c) << 5 |
(str[3] == c) << 4 | (str[4] == c) << 3 | (str[5] == c) << 2 |
(str[6] == c) << 1 | (str[7] == c) << 0;
}

// Class for matching bit patterns such as "x1xx0000" where 'x' is allowed to be
// either 0 or 1.
class BitPattern {
public:
explicit constexpr BitPattern(const char (&str)[9])
: mask_(~ByteMaskString('x', str)),
masked_value_(ByteMaskString('1', str)) {}

bool IsMatch(uint8_t value) const { return masked_value_ == (value & mask_); }

private:
const uint8_t mask_;
const uint8_t masked_value_;
};

// Table for converting between profile_idc/profile_iop to H264Profile.
struct ProfilePattern {
const uint8_t profile_idc;
const BitPattern profile_iop;
const H264Profile profile;
};

// This is from https://tools.ietf.org/html/rfc6184#section-8.1.
constexpr ProfilePattern kProfilePatterns[] = {
{0x42, BitPattern("x1xx0000"), H264Profile::kProfileConstrainedBaseline},
{0x4D, BitPattern("1xxx0000"), H264Profile::kProfileConstrainedBaseline},
{0x58, BitPattern("11xx0000"), H264Profile::kProfileConstrainedBaseline},
{0x42, BitPattern("x0xx0000"), H264Profile::kProfileBaseline},
{0x58, BitPattern("10xx0000"), H264Profile::kProfileBaseline},
{0x4D, BitPattern("0x0x0000"), H264Profile::kProfileMain},
{0x64, BitPattern("00000000"), H264Profile::kProfileHigh},
{0x64, BitPattern("00001100"), H264Profile::kProfileConstrainedHigh},
{0xF4, BitPattern("00000000"), H264Profile::kProfilePredictiveHigh444}};

} // anonymous namespace

std::optional<H264ProfileLevelId> ParseH264ProfileLevelId(const char* str) {
// The string should consist of 3 bytes in hexadecimal format.
if (strlen(str) != 6u)
return std::nullopt;
const uint32_t profile_level_id_numeric = strtol(str, nullptr, 16);
if (profile_level_id_numeric == 0)
return std::nullopt;

// Separate into three bytes.
const uint8_t level_idc =
static_cast<uint8_t>(profile_level_id_numeric & 0xFF);
const uint8_t profile_iop =
static_cast<uint8_t>((profile_level_id_numeric >> 8) & 0xFF);
const uint8_t profile_idc =
static_cast<uint8_t>((profile_level_id_numeric >> 16) & 0xFF);

// Parse level based on level_idc and constraint set 3 flag.
H264Level level_casted = static_cast<H264Level>(level_idc);
H264Level level;

switch (level_casted) {
case H264Level::kLevel1_1:
level = (profile_iop & kConstraintSet3Flag) != 0 ? H264Level::kLevel1_b
: H264Level::kLevel1_1;
break;
case H264Level::kLevel1:
case H264Level::kLevel1_2:
case H264Level::kLevel1_3:
case H264Level::kLevel2:
case H264Level::kLevel2_1:
case H264Level::kLevel2_2:
case H264Level::kLevel3:
case H264Level::kLevel3_1:
case H264Level::kLevel3_2:
case H264Level::kLevel4:
case H264Level::kLevel4_1:
case H264Level::kLevel4_2:
case H264Level::kLevel5:
case H264Level::kLevel5_1:
case H264Level::kLevel5_2:
level = level_casted;
break;
default:
// Unrecognized level_idc.
return std::nullopt;
}

// Parse profile_idc/profile_iop into a Profile enum.
for (const ProfilePattern& pattern : kProfilePatterns) {
if (profile_idc == pattern.profile_idc &&
pattern.profile_iop.IsMatch(profile_iop)) {
return H264ProfileLevelId(pattern.profile, level);
}
}

// Unrecognized profile_idc/profile_iop combination.
return std::nullopt;
}

} // namespace sorac
34 changes: 30 additions & 4 deletions src/signaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,9 @@ class SignalingImpl : public Signaling {
video_lines.assign(it, it2);
}
}

std::optional<sorac::H264ProfileLevelId> h264_profile;

// mid, payload_type, codec
for (const auto& line : video_lines) {
if (auto s = std::string("a=mid:"); starts_with(line, s)) {
Expand All @@ -460,6 +463,25 @@ class SignalingImpl : public Signaling {
cp.name = codec;
rtp_params_.codecs.push_back(cp);
}
} else if (auto s = std::string("a=fmtp:"); starts_with(line, s)) {
// 直前の a=rtpmap が H264 だった場合、a=fmtp 行の profile-level-id を取得する
if (rtp_params_.codecs.empty() ||
rtp_params_.codecs.back().name != "H264") {
continue;
}
auto fmtp = line.substr(s.size());
auto ys = split_with(fmtp, " ");
auto params = split_with(ys[1], ";");
for (const auto& param : params) {
auto zs = split_with(param, "=");
if (zs.size() != 2) {
continue;
}
if (zs[0] == "profile-level-id") {
h264_profile = ParseH264ProfileLevelId(zs[1].c_str());
PLOG_DEBUG << "profile-level-id=" << zs[1];
}
}
}
}
// mid が空ということは vido=false なので何もしない
Expand Down Expand Up @@ -637,7 +659,8 @@ class SignalingImpl : public Signaling {
}
track->setMediaHandler(simulcast_handler);

track->onOpen([this, wtrack = std::weak_ptr<rtc::Track>(track)]() {
track->onOpen([this, wtrack = std::weak_ptr<rtc::Track>(track),
h264_profile]() {
PLOG_DEBUG << "Video Track Opened";
auto track = wtrack.lock();
if (track == nullptr) {
Expand All @@ -646,15 +669,17 @@ class SignalingImpl : public Signaling {

std::function<std::shared_ptr<VideoEncoder>(std::string)>
create_encoder =
[this](std::string codec) -> std::shared_ptr<VideoEncoder> {
[this, h264_profile](
std::string codec) -> std::shared_ptr<VideoEncoder> {
if (codec == "H264") {
if (config_.h264_encoder_type ==
soracp::H264_ENCODER_TYPE_OPEN_H264) {
return CreateOpenH264VideoEncoder(config_.openh264);
} else if (config_.h264_encoder_type ==
soracp::H264_ENCODER_TYPE_VIDEO_TOOLBOX) {
#if defined(__APPLE__)
return CreateVTH26xVideoEncoder(VTH26xVideoEncoderType::kH264);
return CreateVTH26xVideoEncoder(VTH26xVideoEncoderType::kH264,
h264_profile);
#else
PLOG_ERROR << "VideoToolbox is only supported on macOS/iOS";
#endif
Expand All @@ -665,7 +690,8 @@ class SignalingImpl : public Signaling {
if (config_.h265_encoder_type ==
soracp::H265_ENCODER_TYPE_VIDEO_TOOLBOX) {
#if defined(__APPLE__)
return CreateVTH26xVideoEncoder(VTH26xVideoEncoderType::kH265);
return CreateVTH26xVideoEncoder(VTH26xVideoEncoderType::kH265,
std::nullopt);
#else
PLOG_ERROR << "VideoToolbox is only supported on macOS/iOS";
#endif
Expand Down
Loading

0 comments on commit c4d00e2

Please sign in to comment.