简体   繁体   English

如何编写Live555 FramedSource以允许我实时播放H.264

[英]How to write a Live555 FramedSource to allow me to stream H.264 live

I've been trying to write a class that derives from FramedSource in Live555 that will allow me to stream live data from my D3D9 application to an MP4 or similar. 我一直在尝试编写一个从Live555中的FramedSource派生的类,该类将允许我将D3D9应用程序中的实时数据流传输到MP4或类似文件中。

What I do each frame is grab the backbuffer into system memory as a texture, then convert it from RGB -> YUV420P, then encode it using x264, then ideally pass the NAL packets on to Live555. 我要做的每一帧都是将后缓冲作为纹理捕获到系统内存中,然后从RGB-> YUV420P进行转换,然后使用x264进行编码,然后将NAL数据包理想地传递到Live555。 I made a class called H264FramedSource that derived from FramedSource basically by copying the DeviceSource file. 我制作了一个名为H264FramedSource的类,该类基本上是通过复制DeviceSource文件从FramedSource派生的。 Instead of the input being an input file, I've made it a NAL packet which I update each frame. 我将其做成了NAL数据包,而不是输入文件,而是更新了每个帧。

I'm quite new to codecs and streaming, so I could be doing everything completely wrong. 我对编解码器和流媒体很陌生,所以我做的所有事情都可能完全错误。 In each doGetNextFrame() should I be grabbing the NAL packet and doing something like 在每个doGetNextFrame()中,我应该抓取NAL数据包并执行类似的操作

memcpy(fTo, nal->p_payload, nal->i_payload)

I assume that the payload is my frame data in bytes? 我假设有效载荷是我的帧数据(以字节为单位)? If anybody has an example of a class they derived from FramedSource that might at least be close to what I'm trying to do I would love to see it, this is all new to me and a little tricky to figure out what's happening. 如果有人从FramedSource派生了一个类的示例,而该示例可能至少与我想做的很接近,但我很乐意看到它,这对我来说是全新的,而且很难弄清楚正在发生的事情。 Live555's documentation is pretty much the code itself which doesn't exactly make it easy for me to figure out. Live555的文档几乎是代码本身,这并不使我容易理解。

Ok, I finally got some time to spend on this and got it working! 好的,我终于有时间花在此上并使它工作! I'm sure there are others who will be begging to know how to do it so here it is. 我敢肯定还有其他人会乞求知道如何做到这一点。

You will need your own FramedSource to take each frame, encode, and prepare it for streaming, I will provide some of the source code for this soon. 您将需要自己的FramedSource来获取每个帧,进行编码并准备进行流传输,我将为此提供一些源代码。

Essentially throw your FramedSource into the H264VideoStreamDiscreteFramer, then throw this into the H264RTPSink. 本质上,将FramedSource放入H264VideoStreamDiscreteFramer,然后将其放入H264RTPSink。 Something like this 像这样

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();

Now in your main render loop, throw over your backbuffer which you've saved to system memory to your FramedSource so it can be encoded etc. For more info on how to setup the encoding stuff check out this answer How does one encode a series of images into H264 using the x264 C API? 现在,在主渲染循环中,将已保存到系统内存中的backbuffer移至FramedSource,以便对其进行编码等。有关如何设置编码内容的更多信息,请查看此答案如何对一系列使用x264 C API将图像导入H264?

My implementation is very much in a hacky state and is yet to be optimised at all, my d3d application runs at around 15fps due to the encoding, ouch, so I will have to look into this. 我的实现处于非常混乱的状态,并且尚未进行任何优化,由于编码ouch,我的d3d应用程序以大约15fps的速度运行,因此我将不得不对此进行研究。 But for all intents and purposes this StackOverflow question is answered because I was mostly after how to stream it. 但是出于所有意图和目的,都会回答此StackOverflow问题,因为我主要是在寻求如何流式传输它的。 I hope this helps other people. 我希望这对其他人有帮助。

As for my FramedSource it looks a little something like this 至于我的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(&param, "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(&param, "baseline");


        encoder = x264_encoder_open(&param);
        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);
    }
}

Oh and for those who want to know what my concurrent queue is, here it is, and it works brilliantly http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html 哦,对于那些想知道我的并发队列是什么的人来说,它确实很棒,并且可以很好地运行http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition- variables.html

Enjoy and good luck! 祝您好运!

The deliverFrame method lacks the following check at its start: deliveryFrame方法在开始时缺少以下检查:

if (!isCurrentlyAwaitingData()) return;    

see DeviceSource.cpp in LIVE 请参阅LIVE中的DeviceSource.cpp

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM