0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 21:13:04 +02:00
obs-studio/plugins/win-dshow/win-dshow-encoder.cpp
jp9000 6285a47726 (API Change) libobs: Pass type data to get_name callbacks
API changed from:
obs_source_info::get_name(void)
obs_output_info::get_name(void)
obs_encoder_info::get_name(void)
obs_service_info::get_name(void)

API changed to:
obs_source_info::get_name(void *type_data)
obs_output_info::get_name(void *type_data)
obs_encoder_info::get_name(void *type_data)
obs_service_info::get_name(void *type_data)

This allows the type data to be used when getting the name of the
object (useful for plugin wrappers primarily).

NOTE: Though a parameter was added, this is backward-compatible with
older plugins due to calling convention.  The new parameter will simply
be ignored by older plugins, and the stack (if used) will be cleaned up
by the caller.
2015-09-16 09:21:12 -07:00

368 lines
9.4 KiB
C++

#include <obs-module.h>
#include <obs-avc.h>
#include <util/darray.h>
#include <util/dstr.hpp>
#include "libdshowcapture/dshowcapture.hpp"
using namespace DShow;
using namespace std;
struct DShowEncoder {
obs_encoder_t *context;
VideoEncoder encoder;
VideoEncoderConfig config;
const wchar_t *device;
video_format format;
long long frameInterval;
bool first = true;
DARRAY(uint8_t) firstPacket;
DARRAY(uint8_t) header;
inline DShowEncoder(obs_encoder_t *context_, const wchar_t *device_)
: context(context_),
device(device_)
{
da_init(firstPacket);
da_init(header);
}
inline ~DShowEncoder()
{
da_free(firstPacket);
da_free(header);
}
inline void ParseFirstPacket(const uint8_t *data, size_t size);
inline bool Update(obs_data_t *settings);
inline bool Encode(struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet);
};
static const char *GetC985EncoderName(void*)
{
return obs_module_text("Encoder.C985");
}
static const char *GetC353EncoderName(void*)
{
return obs_module_text("Encoder.C353");
}
static inline void FindDevice(DeviceId &id, const wchar_t *name)
{
vector<DeviceId> devices;
DShow::VideoEncoder::EnumEncoders(devices);
for (const DeviceId &device : devices) {
if (device.name.find(name) != string::npos) {
id = device;
break;
}
}
}
/*
* As far as I can tell the devices only seem to support 1024x768 and 1280x720
* resolutions, so I'm just going to cap it to those resolutions by aspect.
*
* XXX: This should really not be hard-coded. Problem is I don't know how to
* properly query the encoding capabilities of the devices.
*/
static const double standardAspect = 1024.0 / 768.0;
static const double wideAspect = 1280.0 / 720.0;
inline bool DShowEncoder::Update(obs_data_t *settings)
{
DStr deviceName;
DeviceId id;
FindDevice(id, device);
video_t *video = obs_encoder_video(context);
const struct video_output_info *voi = video_output_get_info(video);
int bitrate = (int)obs_data_get_int(settings, "bitrate");
int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec");
int width = (int)obs_encoder_get_width(context);
int height = (int)obs_encoder_get_height(context);
double aspect = double(width) / double(height);
if (keyint_sec == 0)
keyint_sec = 2;
if (fabs(aspect - standardAspect) < fabs(aspect - wideAspect)) {
width = 1024;
height = 768;
} else {
width = 1280;
height = 720;
}
int keyint = keyint_sec * voi->fps_num / voi->fps_den;
frameInterval = voi->fps_den * 10000000 / voi->fps_num;
config.fpsNumerator = voi->fps_num;
config.fpsDenominator = voi->fps_den;
config.bitrate = bitrate;
config.keyframeInterval = keyint;
config.cx = width;
config.cy = height;
config.name = id.name;
config.path = id.path;
first = true;
da_resize(firstPacket, 0);
da_resize(header, 0);
dstr_from_wcs(deviceName, id.name.c_str());
DStr encoder_name;
dstr_from_wcs(encoder_name, config.name.c_str());
blog(LOG_DEBUG, "win-dshow-encoder:\n"
"\tencoder: %s\n"
"\twidth: %d\n"
"\theight: %d\n"
"\tfps_num: %d\n"
"\tfps_den: %d",
deviceName->array,
(int)width,
(int)height,
(int)voi->fps_num,
(int)voi->fps_den);
return encoder.SetConfig(config);
}
static bool UpdateDShowEncoder(void *data, obs_data_t *settings)
{
DShowEncoder *encoder = reinterpret_cast<DShowEncoder*>(data);
if (!obs_encoder_active(encoder->context))
return encoder->Update(settings);
return true;
}
static inline void *CreateDShowEncoder(obs_data_t *settings,
obs_encoder_t *context, const wchar_t *device)
{
DShowEncoder *encoder = nullptr;
try {
encoder = new DShowEncoder(context, device);
UpdateDShowEncoder(encoder, settings);
} catch (const char *error) {
blog(LOG_ERROR, "Could not create DirectShow encoder '%s': %s",
obs_encoder_get_name(context), error);
}
UNUSED_PARAMETER(settings);
return encoder;
}
static void *CreateC985Encoder(obs_data_t *settings, obs_encoder_t *context)
{
return CreateDShowEncoder(settings, context, L"C985");
}
static void *CreateC353Encoder(obs_data_t *settings, obs_encoder_t *context)
{
return CreateDShowEncoder(settings, context, L"C353");
}
static void DestroyDShowEncoder(void *data)
{
delete reinterpret_cast<DShowEncoder*>(data);
}
/* the first packet contains the SPS/PPS (header) NALs, so parse the first
* packet and separate the NALs */
inline void DShowEncoder::ParseFirstPacket(const uint8_t *data, size_t size)
{
const uint8_t *nal_start, *nal_end, *nal_codestart;
const uint8_t *end = data + size;
int type;
nal_start = obs_avc_find_startcode(data, end);
nal_end = nullptr;
while (nal_end != end) {
nal_codestart = nal_start;
while (nal_start < end && !*(nal_start++));
if (nal_start == end)
break;
type = nal_start[0] & 0x1F;
nal_end = obs_avc_find_startcode(nal_start, end);
if (!nal_end)
nal_end = end;
if (type == OBS_NAL_SPS || type == OBS_NAL_PPS) {
da_push_back_array(header, nal_codestart,
nal_end - nal_codestart);
} else {
da_push_back_array(firstPacket, nal_codestart,
nal_end - nal_codestart);
}
nal_start = nal_end;
}
}
inline bool DShowEncoder::Encode(struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
{
unsigned char *frame_data[DSHOW_MAX_PLANES] = {};
size_t frame_sizes[DSHOW_MAX_PLANES] = {};
EncoderPacket dshowPacket;
bool new_packet = false;
/* The encoders expect YV12, so swap the chroma planes for encoding */
if (format == VIDEO_FORMAT_I420) {
frame_data[0] = frame->data[0];
frame_data[1] = frame->data[2];
frame_data[2] = frame->data[1];
frame_sizes[0] = frame->linesize[0] * config.cy;
frame_sizes[1] = frame->linesize[2] * config.cy / 2;
frame_sizes[2] = frame->linesize[1] * config.cy / 2;
}
long long actualPTS = frame->pts * frameInterval;
bool success = encoder.Encode(frame_data, frame_sizes,
actualPTS, actualPTS + frameInterval,
dshowPacket, new_packet);
if (!success)
return false;
if (new_packet && !!dshowPacket.data && !!dshowPacket.size) {
packet->data = dshowPacket.data;
packet->size = dshowPacket.size;
packet->type = OBS_ENCODER_VIDEO;
packet->pts = dshowPacket.pts / frameInterval;
packet->dts = dshowPacket.dts / frameInterval;
packet->keyframe = obs_avc_keyframe(packet->data, packet->size);
/* first packet must be parsed in order to retrieve header */
if (first) {
first = false;
ParseFirstPacket(packet->data, packet->size);
packet->data = firstPacket.array;
packet->size = firstPacket.num;
}
*received_packet = true;
}
return true;
}
static bool DShowEncode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
{
return reinterpret_cast<DShowEncoder*>(data)->Encode(frame, packet,
received_packet);
}
static bool GetDShowExtraData(void *data, uint8_t **extra_data, size_t *size)
{
DShowEncoder *encoder = reinterpret_cast<DShowEncoder*>(data);
*extra_data = encoder->header.array;
*size = encoder->header.num;
return *size > 0;
}
static inline bool ValidResolution(uint32_t width, uint32_t height)
{
return (width == 1280 && height == 720) ||
(width == 1024 && height == 768);
}
static void GetDShowVideoInfo(void *data, struct video_scale_info *info)
{
DShowEncoder *encoder = reinterpret_cast<DShowEncoder*>(data);
encoder->format = VIDEO_FORMAT_I420;
if (info->format == VIDEO_FORMAT_I420 &&
ValidResolution(info->width, info->height))
return;
info->format = VIDEO_FORMAT_I420;
info->width = info->width;
info->height = info->height;
info->range = VIDEO_RANGE_DEFAULT;
info->colorspace = VIDEO_CS_DEFAULT;
double aspect = double(info->width) / double(info->height);
if (fabs(aspect - standardAspect) < fabs(aspect - wideAspect)) {
info->width = 1024;
info->height = 768;
} else {
info->width = 1280;
info->height = 720;
}
}
static void GetDShowEncoderDefauts(obs_data_t *settings)
{
obs_data_set_default_int(settings, "bitrate", 1000);
}
static obs_properties_t *GetDShowEncoderProperties(void *data)
{
obs_properties_t *ppts = obs_properties_create();
obs_properties_add_int(ppts, "bitrate", obs_module_text("Bitrate"),
1000, 60000, 1);
UNUSED_PARAMETER(data);
return ppts;
}
void RegisterDShowEncoders()
{
obs_encoder_info info = {};
info.type = OBS_ENCODER_VIDEO;
info.codec = "h264";
info.destroy = DestroyDShowEncoder;
info.encode = DShowEncode;
info.update = UpdateDShowEncoder;
info.get_defaults = GetDShowEncoderDefauts;
info.get_properties = GetDShowEncoderProperties;
info.get_extra_data = GetDShowExtraData;
info.get_video_info = GetDShowVideoInfo;
vector<DeviceId> devices;
DShow::VideoEncoder::EnumEncoders(devices);
bool foundC985 = false;
for (const DeviceId &device : devices) {
if (!foundC985 && device.name.find(L"C985") != string::npos) {
info.id = "dshow_c985_h264";
info.get_name = GetC985EncoderName;
info.create = CreateC985Encoder;
obs_register_encoder(&info);
foundC985 = true;
} else if (device.name.find(L"C353") != string::npos) {
info.id = "dshow_c353_h264";
info.get_name = GetC353EncoderName;
info.create = CreateC353Encoder;
obs_register_encoder(&info);
}
}
}