Я пытался написать класс, производный от FramedSource в Live555, который позволил бы мне передавать потоковые данные из моего приложения D3D9 в MP4 или подобное.
То, что я делаю каждый кадр — это захват буфера в системную память в качестве текстуры, затем преобразование его из RGB -> YUV420P, затем кодирование его с использованием x264, а затем, в идеале, передачу пакетов NAL в Live555. Я создал класс H264FramedSource, который был основан на FramedSource путем копирования файла DeviceSource. Вместо того, чтобы ввод был входным файлом, я сделал его пакетом NAL, который я обновляю каждый кадр.
Я довольно плохо знаком с кодеками и потоковой передачей, так что я могу делать все совершенно неправильно. В каждом doGetNextFrame () я должен захватывать пакет NAL и делать что-то вроде
memcpy(fTo, nal->p_payload, nal->i_payload)
Я предполагаю, что полезная нагрузка — это данные моего кадра в байтах? Если у кого-нибудь есть пример класса, созданного на основе FramedSource, который, по крайней мере, может быть близок к тому, что я пытаюсь сделать, я бы хотел его увидеть, это все для меня ново и немного сложно понять, что происходит. Документация Live555 в значительной степени представляет собой сам код, который не совсем облегчает мне понимание.
Хорошо, у меня наконец-то появилось время потратить на это, и он заработал! Я уверен, что есть другие, которые будут просить знать, как это сделать, так что вот оно.
Вам понадобится ваш собственный FramedSource, чтобы взять каждый кадр, кодировать и подготовить его к потоковой передаче. Вскоре я предоставлю часть исходного кода для этого.
По сути, добавьте ваш FramedSource в H264VideoStreamDiscreteFramer, затем добавьте его в H264RTPSink. Что-то вроде этого
scheduler = BasicTaskScheduler::createNew();
env = BasicUsageEnvironment::createNew(*scheduler);
framedSource = H264FramedSource::createNew(*env, 0,0);
h264VideoStreamDiscreteFramer
= H264VideoStreamDiscreteFramer::createNew(*env, framedSource);
// initialise the RTP Sink stuff here, look at
// testH264VideoStreamer.cpp to find out how
videoSink->startPlaying(*h264VideoStreamDiscreteFramer, NULL, videoSink);
env->taskScheduler().doEventLoop();
Теперь в вашем основном цикле рендеринга, перекиньте свой буферный буфер, который вы сохранили в системную память, на ваш FramedSource, чтобы он мог быть закодирован и т. Д. Для получения дополнительной информации о том, как настроить кодирование, посмотрите этот ответ. Как можно закодировать серию изображений в H264 с помощью x264 C API?
Моя реализация находится в очень взломанном состоянии и еще не оптимизирована вообще, мое приложение d3d работает со скоростью около 15 кадров в секунду из-за кодировки, так что я должен разобраться с этим. Но для всех намерений и целей ответ на этот вопрос StackOverflow, потому что я был в основном после того, как его поток. Я надеюсь, что это помогает другим людям.
Что касается моего FramedSource, он выглядит примерно так
concurrent_queue<x264_nal_t> m_queue;
SwsContext* convertCtx;
x264_param_t param;
x264_t* encoder;
x264_picture_t pic_in, pic_out;EventTriggerId H264FramedSource::eventTriggerId = 0;
unsigned H264FramedSource::FrameSize = 0;
unsigned H264FramedSource::referenceCount = 0;
int W = 720;
int H = 960;
H264FramedSource* H264FramedSource::createNew(UsageEnvironment& env,
unsigned preferredFrameSize,
unsigned playTimePerFrame)
{
return new H264FramedSource(env, preferredFrameSize, playTimePerFrame);
}
H264FramedSource::H264FramedSource(UsageEnvironment& env,
unsigned preferredFrameSize,
unsigned playTimePerFrame)
: FramedSource(env),
fPreferredFrameSize(fMaxSize),
fPlayTimePerFrame(playTimePerFrame),
fLastPlayTime(0),
fCurIndex(0)
{
if (referenceCount == 0)
{
}
++referenceCount;
x264_param_default_preset(¶m, "veryfast", "zerolatency");
param.i_threads = 1;
param.i_width = 720;
param.i_height = 960;
param.i_fps_num = 60;
param.i_fps_den = 1;
// Intra refres:
param.i_keyint_max = 60;
param.b_intra_refresh = 1;
//Rate control:
param.rc.i_rc_method = X264_RC_CRF;
param.rc.f_rf_constant = 25;
param.rc.f_rf_constant_max = 35;
param.i_sps_id = 7;
//For streaming:
param.b_repeat_headers = 1;
param.b_annexb = 1;
x264_param_apply_profile(¶m, "baseline");encoder = x264_encoder_open(¶m);
pic_in.i_type = X264_TYPE_AUTO;
pic_in.i_qpplus1 = 0;
pic_in.img.i_csp = X264_CSP_I420;
pic_in.img.i_plane = 3;x264_picture_alloc(&pic_in, X264_CSP_I420, 720, 920);
convertCtx = sws_getContext(720, 960, PIX_FMT_RGB24, 720, 760, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);if (eventTriggerId == 0)
{
eventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0);
}
}
H264FramedSource::~H264FramedSource()
{
--referenceCount;
if (referenceCount == 0)
{
// Reclaim our 'event trigger'
envir().taskScheduler().deleteEventTrigger(eventTriggerId);
eventTriggerId = 0;
}
}
void H264FramedSource::AddToBuffer(uint8_t* buf, int surfaceSizeInBytes)
{
uint8_t* surfaceData = (new uint8_t[surfaceSizeInBytes]);
memcpy(surfaceData, buf, surfaceSizeInBytes);
int srcstride = W*3;
sws_scale(convertCtx, &surfaceData, &srcstride,0, H, pic_in.img.plane, pic_in.img.i_stride);
x264_nal_t* nals = NULL;
int i_nals = 0;
int frame_size = -1;frame_size = x264_encoder_encode(encoder, &nals, &i_nals, &pic_in, &pic_out);
static bool finished = false;
if (frame_size >= 0)
{
static bool alreadydone = false;
if(!alreadydone)
{
x264_encoder_headers(encoder, &nals, &i_nals);
alreadydone = true;
}
for(int i = 0; i < i_nals; ++i)
{
m_queue.push(nals[i]);
}
}
delete [] surfaceData;
surfaceData = NULL;
envir().taskScheduler().triggerEvent(eventTriggerId, this);
}
void H264FramedSource::doGetNextFrame()
{
deliverFrame();
}
void H264FramedSource::deliverFrame0(void* clientData)
{
((H264FramedSource*)clientData)->deliverFrame();
}
void H264FramedSource::deliverFrame()
{
x264_nal_t nalToDeliver;
if (fPlayTimePerFrame > 0 && fPreferredFrameSize > 0) {
if (fPresentationTime.tv_sec == 0 && fPresentationTime.tv_usec == 0) {
// This is the first frame, so use the current time:
gettimeofday(&fPresentationTime, NULL);
} else {
// Increment by the play time of the previous data:
unsigned uSeconds = fPresentationTime.tv_usec + fLastPlayTime;
fPresentationTime.tv_sec += uSeconds/1000000;
fPresentationTime.tv_usec = uSeconds%1000000;
}
// Remember the play time of this data:
fLastPlayTime = (fPlayTimePerFrame*fFrameSize)/fPreferredFrameSize;
fDurationInMicroseconds = fLastPlayTime;
} else {
// We don't know a specific play time duration for this data,
// so just record the current time as being the 'presentation time':
gettimeofday(&fPresentationTime, NULL);
}
if(!m_queue.empty())
{
m_queue.wait_and_pop(nalToDeliver);
uint8_t* newFrameDataStart = (uint8_t*)0xD15EA5E;
newFrameDataStart = (uint8_t*)(nalToDeliver.p_payload);
unsigned newFrameSize = nalToDeliver.i_payload;
// Deliver the data here:
if (newFrameSize > fMaxSize) {
fFrameSize = fMaxSize;
fNumTruncatedBytes = newFrameSize - fMaxSize;
}
else {
fFrameSize = newFrameSize;
}
memcpy(fTo, nalToDeliver.p_payload, nalToDeliver.i_payload);
FramedSource::afterGetting(this);
}
}
Да, и для тех, кто хочет знать, какова моя параллельная очередь, вот она, и она работает блестяще http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html
Наслаждайтесь и удачи!
deliverFrame В начале метода не хватает следующей проверки:
if (!isCurrentlyAwaitingData()) return;
смотрите DeviceSource.cpp в LIVE