2014-12-19 12:31:23 -08:00
|
|
|
#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 {
|
2019-06-22 22:13:45 -07:00
|
|
|
obs_encoder_t *context;
|
|
|
|
VideoEncoder encoder;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
VideoEncoderConfig config;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
const wchar_t *device;
|
|
|
|
video_format format;
|
|
|
|
long long frameInterval;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
bool first = true;
|
|
|
|
DARRAY(uint8_t) firstPacket;
|
|
|
|
DARRAY(uint8_t) header;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
inline DShowEncoder(obs_encoder_t *context_, const wchar_t *device_)
|
2019-06-22 22:13:45 -07:00
|
|
|
: context(context_), device(device_)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
|
|
|
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,
|
2019-06-22 22:13:45 -07:00
|
|
|
struct encoder_packet *packet,
|
|
|
|
bool *received_packet);
|
2014-12-19 12:31:23 -08:00
|
|
|
};
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
static const char *GetC985EncoderName(void *)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
2014-12-21 10:41:30 -08:00
|
|
|
return obs_module_text("Encoder.C985");
|
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
static const char *GetC353EncoderName(void *)
|
2014-12-21 10:41:30 -08:00
|
|
|
{
|
|
|
|
return obs_module_text("Encoder.C353");
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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);
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
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());
|
2019-06-22 22:13:45 -07:00
|
|
|
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);
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
return encoder.SetConfig(config);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool UpdateDShowEncoder(void *data, obs_data_t *settings)
|
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
DShowEncoder *encoder = reinterpret_cast<DShowEncoder *>(data);
|
2015-02-14 15:02:49 -08:00
|
|
|
|
|
|
|
if (!obs_encoder_active(encoder->context))
|
|
|
|
return encoder->Update(settings);
|
|
|
|
|
|
|
|
return true;
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void *CreateDShowEncoder(obs_data_t *settings,
|
2019-06-22 22:13:45 -07:00
|
|
|
obs_encoder_t *context,
|
|
|
|
const wchar_t *device)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
|
|
|
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",
|
2019-06-22 22:13:45 -07:00
|
|
|
obs_encoder_get_name(context), error);
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
delete reinterpret_cast<DShowEncoder *>(data);
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
while (nal_start < end && !*(nal_start++))
|
|
|
|
;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
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,
|
2019-06-22 22:13:45 -07:00
|
|
|
nal_end - nal_codestart);
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
} else {
|
|
|
|
da_push_back_array(firstPacket, nal_codestart,
|
2019-06-22 22:13:45 -07:00
|
|
|
nal_end - nal_codestart);
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
nal_start = nal_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool DShowEncoder::Encode(struct encoder_frame *frame,
|
2019-06-22 22:13:45 -07:00
|
|
|
struct encoder_packet *packet,
|
|
|
|
bool *received_packet)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
bool success = encoder.Encode(frame_data, frame_sizes, actualPTS,
|
|
|
|
actualPTS + frameInterval, dshowPacket,
|
|
|
|
new_packet);
|
2014-12-19 12:31:23 -08:00
|
|
|
if (!success)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (new_packet && !!dshowPacket.data && !!dshowPacket.size) {
|
2019-06-22 22:13:45 -07:00
|
|
|
packet->data = dshowPacket.data;
|
|
|
|
packet->size = dshowPacket.size;
|
|
|
|
packet->type = OBS_ENCODER_VIDEO;
|
|
|
|
packet->pts = dshowPacket.pts / frameInterval;
|
|
|
|
packet->dts = dshowPacket.dts / frameInterval;
|
2014-12-19 12:31:23 -08:00
|
|
|
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,
|
2019-06-22 22:13:45 -07:00
|
|
|
struct encoder_packet *packet, bool *received_packet)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
return reinterpret_cast<DShowEncoder *>(data)->Encode(frame, packet,
|
|
|
|
received_packet);
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool GetDShowExtraData(void *data, uint8_t **extra_data, size_t *size)
|
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
DShowEncoder *encoder = reinterpret_cast<DShowEncoder *>(data);
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
*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) ||
|
2019-06-22 22:13:45 -07:00
|
|
|
(width == 1024 && height == 768);
|
2014-12-19 12:31:23 -08:00
|
|
|
}
|
|
|
|
|
2015-04-17 01:34:10 -07:00
|
|
|
static void GetDShowVideoInfo(void *data, struct video_scale_info *info)
|
2014-12-19 12:31:23 -08:00
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
DShowEncoder *encoder = reinterpret_cast<DShowEncoder *>(data);
|
2014-12-19 12:31:23 -08:00
|
|
|
encoder->format = VIDEO_FORMAT_I420;
|
|
|
|
|
2015-04-17 01:34:10 -07:00
|
|
|
if (info->format == VIDEO_FORMAT_I420 &&
|
|
|
|
ValidResolution(info->width, info->height))
|
|
|
|
return;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
info->format = VIDEO_FORMAT_I420;
|
|
|
|
info->width = info->width;
|
|
|
|
info->height = info->height;
|
|
|
|
info->range = VIDEO_RANGE_DEFAULT;
|
2014-12-19 12:31:23 -08:00
|
|
|
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"),
|
2019-06-22 22:13:45 -07:00
|
|
|
1000, 60000, 1);
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
UNUSED_PARAMETER(data);
|
|
|
|
return ppts;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RegisterDShowEncoders()
|
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
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;
|
2014-12-19 12:31:23 -08:00
|
|
|
|
|
|
|
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";
|
2014-12-21 10:41:30 -08:00
|
|
|
info.get_name = GetC985EncoderName;
|
2014-12-19 12:31:23 -08:00
|
|
|
info.create = CreateC985Encoder;
|
|
|
|
obs_register_encoder(&info);
|
|
|
|
foundC985 = true;
|
|
|
|
|
|
|
|
} else if (device.name.find(L"C353") != string::npos) {
|
|
|
|
info.id = "dshow_c353_h264";
|
2014-12-21 10:41:30 -08:00
|
|
|
info.get_name = GetC353EncoderName;
|
2014-12-19 12:31:23 -08:00
|
|
|
info.create = CreateC353Encoder;
|
|
|
|
obs_register_encoder(&info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|