Skip to content

Commit

Permalink
LLHLS ID3v2 Timed Metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
getroot committed Dec 9, 2022
1 parent 90681e1 commit 9d64404
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include "stream_actions_controller.h"
#include "../../../../../api_private.h"

#include <base/provider/application.h>
#include <modules/id3v2/id3v2.h>
#include <modules/id3v2/frames/id3v2_frames.h>

namespace api
{
namespace v1
Expand All @@ -20,6 +24,7 @@ namespace api
RegisterPost(R"((hlsDumps))", &StreamActionsController::OnPostHLSDumps);
RegisterPost(R"((startHlsDump))", &StreamActionsController::OnPostStartHLSDump);
RegisterPost(R"((stopHlsDump))", &StreamActionsController::OnPostStopHLSDump);
RegisterPost(R"((sendEvent))", &StreamActionsController::OnPostSendEvent);
}

// POST /v1/vhosts/<vhost_name>/apps/<app_name>/streams/<stream_name>:hlsDumps
Expand Down Expand Up @@ -211,6 +216,140 @@ namespace api
return {http::StatusCode::OK};
}

// POST /v1/vhosts/<vhost_name>/apps/<app_name>/streams/<stream_name>:injectHLSEvent
ApiResponse StreamActionsController::OnPostSendEvent(const std::shared_ptr<http::svr::HttpExchange> &client, const Json::Value &request_body,
const std::shared_ptr<mon::HostMetrics> &vhost,
const std::shared_ptr<mon::ApplicationMetrics> &app,
const std::shared_ptr<mon::StreamMetrics> &stream,
const std::vector<std::shared_ptr<mon::StreamMetrics>> &output_streams)
{
// Validate request body
// {
// "eventFormat": "id3v2",
// "eventType": "video",
// "events":[
// {
// "frameType": "TXXX",
// "info": "AirenSoft",
// "data": "OvenMediaEngine"
// },
// {
// "frameType": "TXXX",
// "info": "AirenSoft",
// "data": "OvenMediaEngine"
// }
// ]
// }

if (request_body.isMember("eventFormat") == false || request_body["eventFormat"].isString() == false ||
request_body.isMember("events") == false || request_body["events"].isArray() == false)
{
throw http::HttpError(http::StatusCode::BadRequest, "eventFormat(string) and events(array) are required");
}

// Now only support ID3v2 format
ov::String event_format_string = request_body["eventFormat"].asString().c_str();
if (event_format_string.UpperCaseString() != "ID3V2")
{
throw http::HttpError(http::StatusCode::BadRequest, "eventFormat is not supported: [%s]", event_format_string.CStr());
}

cmn::BitstreamFormat event_format = cmn::BitstreamFormat::ID3v2;

auto events = request_body["events"];
if (events.size() == 0)
{
throw http::HttpError(http::StatusCode::BadRequest, "events is empty");
}

// Make ID3v2 tags
auto id3v2_event = std::make_shared<ID3v2>();
id3v2_event->SetVersion(4, 0);
for (const auto &event : events)
{
if (event.isMember("frameType") == false || event["frameType"].isString() == false)
{
throw http::HttpError(http::StatusCode::BadRequest, "frameType is required in events");
}

ov::String frame_type = event["frameType"].asString().c_str();
ov::String info;
ov::String data;

if (event.isMember("info") == true && event["info"].isString() == true)
{
info = event["info"].asString().c_str();
}

if (event.isMember("data") == true && event["data"].isString() == true)
{
data = event["data"].asString().c_str();
}

std::shared_ptr<ID3v2Frame> frame;
if (frame_type.UpperCaseString() == "TXXX")
{
frame = std::make_shared<ID3v2TxxxFrame>(info, data);
}
else if (frame_type.UpperCaseString().Get(0) == 'T')
{
frame = std::make_shared<ID3v2TextFrame>(frame_type, data);
}
else
{
throw http::HttpError(http::StatusCode::BadRequest, "frameType is not supported: [%s]", frame_type.CStr());
}

id3v2_event->AddFrame(frame);
}

// Event Type
cmn::PacketType event_type = cmn::PacketType::EVENT;
if (request_body.isMember("eventType") == true)
{
auto event_type_json = request_body["eventType"];
if (event_type_json.isString() == false)
{
throw http::HttpError(http::StatusCode::BadRequest, "eventType must be string");
}

ov::String event_type_string = event_type_json.asString().c_str();
if (event_type_string.UpperCaseString() == "VIDEO")
{
event_type = cmn::PacketType::VIDEO_EVENT;
}
else if (event_type_string.UpperCaseString() == "AUDIO")
{
event_type = cmn::PacketType::AUDIO_EVENT;
}
else if (event_type_string.UpperCaseString() == "EVENT")
{
event_type = cmn::PacketType::EVENT;
}
else
{
throw http::HttpError(http::StatusCode::BadRequest, "eventType is not supported: [%s]", event_type_string.CStr());
}
}

auto source_stream = GetSourceStream(stream);
if (source_stream == nullptr)
{
throw http::HttpError(http::StatusCode::NotFound,
"Could not find stream: [%s/%s/%s]",
vhost->GetName().CStr(), app->GetName().GetAppName().CStr(), stream->GetName().CStr());
}

if (source_stream->SendDataFrame(-1, event_format, event_type, id3v2_event->Serialize()) == false)
{
throw http::HttpError(http::StatusCode::InternalServerError,
"Internal Server Error - Could not inject event: [%s/%s/%s]",
vhost->GetName().CStr(), app->GetName().GetAppName().CStr(), stream->GetName().CStr());
}

return {http::StatusCode::OK};
}

ApiResponse StreamActionsController::OnGetDummyAction(const std::shared_ptr<http::svr::HttpExchange> &client,
const std::shared_ptr<mon::HostMetrics> &vhost,
const std::shared_ptr<mon::ApplicationMetrics> &app,
Expand All @@ -222,6 +361,58 @@ namespace api
return app->GetConfig().ToJson();
}

std::shared_ptr<pvd::Stream> StreamActionsController::GetSourceStream(const std::shared_ptr<mon::StreamMetrics> &stream)
{
// Get PrivderType from SourceType
ProviderType provider_type = ProviderType::Unknown;
switch (stream->GetSourceType())
{
case StreamSourceType::WebRTC:
provider_type = ProviderType::WebRTC;
break;
case StreamSourceType::Ovt:
provider_type = ProviderType::Ovt;
break;
case StreamSourceType::Rtmp:
provider_type = ProviderType::Rtmp;
break;
case StreamSourceType::Rtsp:
provider_type = ProviderType::Rtsp;
break;
case StreamSourceType::RtspPull:
provider_type = ProviderType::RtspPull;
break;
case StreamSourceType::Mpegts:
provider_type = ProviderType::Mpegts;
break;
case StreamSourceType::Srt:
provider_type = ProviderType::Srt;
break;

case StreamSourceType::File:
provider_type = ProviderType::File;
break;
case StreamSourceType::RtmpPull:
case StreamSourceType::Transcoder:
default:
return nullptr;
}

auto provider = std::dynamic_pointer_cast<pvd::Provider>(ocst::Orchestrator::GetInstance()->GetProviderFromType(provider_type));
if (provider == nullptr)
{
return nullptr;
}

auto application = provider->GetApplicationByName(stream->GetApplicationInfo().GetName());
if (application == nullptr)
{
return nullptr;
}

return application->GetStreamByName(stream->GetName());
}

std::shared_ptr<LLHlsStream> StreamActionsController::GetLLHlsStream(const std::shared_ptr<mon::StreamMetrics> &stream_metric)
{
auto publisher = std::dynamic_pointer_cast<LLHlsPublisher>(ocst::Orchestrator::GetInstance()->GetPublisherFromType(PublisherType::LLHls));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,23 @@ namespace api
const std::shared_ptr<mon::StreamMetrics> &stream,
const std::vector<std::shared_ptr<mon::StreamMetrics>> &output_streams);

// POST /v1/vhosts/<vhost_name>/apps/<app_name>/streams/<stream_name>:sendEvent
ApiResponse OnPostSendEvent(const std::shared_ptr<http::svr::HttpExchange> &client, const Json::Value &request_body,
const std::shared_ptr<mon::HostMetrics> &vhost,
const std::shared_ptr<mon::ApplicationMetrics> &app,
const std::shared_ptr<mon::StreamMetrics> &stream,
const std::vector<std::shared_ptr<mon::StreamMetrics>> &output_streams);

// GET /v1/vhosts/<vhost_name>/apps/<app_name>/streams/<stream_name>:<action>
ApiResponse OnGetDummyAction(const std::shared_ptr<http::svr::HttpExchange> &client,
const std::shared_ptr<mon::HostMetrics> &vhost,
const std::shared_ptr<mon::ApplicationMetrics> &app,
const std::shared_ptr<mon::StreamMetrics> &stream,
const std::vector<std::shared_ptr<mon::StreamMetrics>> &output_streams);
private:

// TODO(Getroot): Move to mon::StreamMetrics
std::shared_ptr<pvd::Stream> GetSourceStream(const std::shared_ptr<mon::StreamMetrics> &stream);
std::shared_ptr<LLHlsStream> GetLLHlsStream(const std::shared_ptr<mon::StreamMetrics> &stream);
};
}
Expand Down
18 changes: 18 additions & 0 deletions src/projects/base/info/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,24 @@ namespace info
_representation_type = type;
}

int32_t Stream::IssueUniqueTrackId()
{
int32_t track_id = ov::Random::GenerateInt32(100, 0x7FFFFFFF);

while (true)
{
auto item = _tracks.find(track_id);
if (item == _tracks.end())
{
break;
}

track_id = ov::Random::GenerateInt32(100, 0x7FFFFFFF);
}

return track_id;
}

bool Stream::AddTrack(const std::shared_ptr<MediaTrack> &track)
{
// If there is an existing track with the same track id, it will be deleted.
Expand Down
1 change: 1 addition & 0 deletions src/projects/base/info/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace info
StreamRepresentationType GetRepresentationType() const;
void SetRepresentationType(const StreamRepresentationType &type);

int32_t IssueUniqueTrackId();
bool AddTrack(const std::shared_ptr<MediaTrack> &track);
const std::shared_ptr<MediaTrack> GetTrack(int32_t id) const;
const std::shared_ptr<MediaTrack> GetTrack(const ov::String &name) const;
Expand Down
1 change: 1 addition & 0 deletions src/projects/base/mediarouter/media_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace cmn
NALU, // For H.264 AVCC, ANNEXB

// For Data Track
EVENT,
VIDEO_EVENT,
AUDIO_EVENT,
};
Expand Down
15 changes: 15 additions & 0 deletions src/projects/base/provider/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ namespace pvd
}
}

// If there is no data track, add data track
if (stream->GetFirstTrack(cmn::MediaType::Data) == nullptr)
{
// Add data track
auto data_track = std::make_shared<MediaTrack>();

// Issue unique track id
data_track->SetId(stream->IssueUniqueTrackId());
data_track->SetMediaType(cmn::MediaType::Data);
data_track->SetTimeBase(1, 1000);
data_track->SetOriginBitstream(cmn::BitstreamFormat::Unknown);

stream->AddTrack(data_track);
}

stream->SetApplication(GetSharedPtrAs<Application>());
stream->SetApplicationInfo(GetSharedPtrAs<Application>());

Expand Down
26 changes: 26 additions & 0 deletions src/projects/base/provider/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,32 @@ namespace pvd
return GetApplication()->GetApplicationTypeName();
}

bool Stream::SendDataFrame(int64_t timestamp, const cmn::BitstreamFormat &format, const cmn::PacketType &packet_type, const std::shared_ptr<ov::Data> &frame)
{
if (frame == nullptr)
{
return false;
}

auto data_track = GetFirstTrack(cmn::MediaType::Data);
if (data_track == nullptr)
{
logte("Data track is not found. %s/%s(%u)", GetApplicationName(), GetName().CStr(), GetId());
return false;
}

auto event_message = std::make_shared<MediaPacket>(GetMsid(),
cmn::MediaType::Data,
data_track->GetId(),
frame,
timestamp,
timestamp,
format,
packet_type);

return SendFrame(event_message);
}

bool Stream::SendFrame(const std::shared_ptr<MediaPacket> &packet)
{
if (_application == nullptr)
Expand Down
2 changes: 2 additions & 0 deletions src/projects/base/provider/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ namespace pvd
virtual bool Stop();
virtual bool Terminate();

bool SendDataFrame(int64_t timestamp, const cmn::BitstreamFormat &format, const cmn::PacketType &packet_type, const std::shared_ptr<ov::Data> &frame);

protected:
Stream(const std::shared_ptr<pvd::Application> &application, StreamSourceType source_type);
Stream(const std::shared_ptr<pvd::Application> &application, info::stream_id_t stream_id, StreamSourceType source_type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ namespace cfg
struct HLSID3v2 : public Item
{
protected:
ov::String _inject_to; // video, audio, both
ov::String _frame_type; // TXXX, WXXX, PRIV, T???, W???
ov::String _event_type; // video, audio, both
ov::String _frame_type; // TXXX, T???
ov::String _info;
ov::String _data;

public:
CFG_DECLARE_CONST_REF_GETTER_OF(GetInjectTo, _inject_to);
CFG_DECLARE_CONST_REF_GETTER_OF(GetEventType, _event_type);
CFG_DECLARE_CONST_REF_GETTER_OF(GetFrameType, _frame_type);
CFG_DECLARE_CONST_REF_GETTER_OF(GetInfo, _info);
CFG_DECLARE_CONST_REF_GETTER_OF(GetData, _data);

protected:
void MakeList() override
{
Register("InjectTo", &_inject_to);
Register("EventType", &_event_type);
Register("FrameType", &_frame_type);
Register<Optional>("Info", &_info);
Register("Data", &_data);
Expand Down
Loading

0 comments on commit 9d64404

Please sign in to comment.