Live555: источник X264 Stream Live на основе testOnDemandRTSPServer

Я пытаюсь создать сервер rtsp, который транслирует вывод OpenGL моей программы. Я просмотрел Как написать Live555 FramedSource, чтобы позволить мне транслировать H.264 в прямом эфире, но мне нужно, чтобы поток был одноадресным. Итак, я посмотрел testOnDemandRTSPServer. Использование того же кода не удается. Насколько я понимаю, мне нужно предоставить память, в которой я храню свои кадры h264, чтобы OnDemandServer мог читать их по запросу.

H264VideoStreamServerMediaSubsession.cpp

H264VideoStreamServerMediaSubsession*
H264VideoStreamServerMediaSubsession::createNew(UsageEnvironment& env,
                          Boolean reuseFirstSource) {
  return new H264VideoStreamServerMediaSubsession(env, reuseFirstSource);
}

H264VideoStreamServerMediaSubsession::H264VideoStreamServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource)
  : OnDemandServerMediaSubsession(env, reuseFirstSource), fAuxSDPLine(NULL), fDoneFlag(0), fDummyRTPSink(NULL) {
}

H264VideoStreamServerMediaSubsession::~H264VideoStreamServerMediaSubsession() {
  delete[] fAuxSDPLine;
}

static void afterPlayingDummy(void* clientData) {
  H264VideoStreamServerMediaSubsession* subsess = (H264VideoStreamServerMediaSubsession*)clientData;
  subsess->afterPlayingDummy1();
}

void H264VideoStreamServerMediaSubsession::afterPlayingDummy1() {
  // Unschedule any pending 'checking' task:
  envir().taskScheduler().unscheduleDelayedTask(nextTask());
  // Signal the event loop that we're done:
  setDoneFlag();
}

static void checkForAuxSDPLine(void* clientData) {
  H264VideoStreamServerMediaSubsession* subsess = (H264VideoStreamServerMediaSubsession*)clientData;
  subsess->checkForAuxSDPLine1();
}

void H264VideoStreamServerMediaSubsession::checkForAuxSDPLine1() {
  char const* dasl;

  if (fAuxSDPLine != NULL) {
    // Signal the event loop that we're done:
    setDoneFlag();
  } else if (fDummyRTPSink != NULL && (dasl = fDummyRTPSink->auxSDPLine()) != NULL) {
    fAuxSDPLine = strDup(dasl);
    fDummyRTPSink = NULL;

    // Signal the event loop that we're done:
    setDoneFlag();
  } else {
    // try again after a brief delay:
    int uSecsToDelay = 100000; // 100 ms
    nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
                  (TaskFunc*)checkForAuxSDPLine, this);
  }
}

char const* H264VideoStreamServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource) {
  if (fAuxSDPLine != NULL) return fAuxSDPLine; // it's already been set up (for a previous client)

  if (fDummyRTPSink == NULL) { // we're not already setting it up for another, concurrent stream
    // Note: For H264 video files, the 'config' information ("profile-level-id" and "sprop-parameter-sets") isn't known
    // until we start reading the file.  This means that "rtpSink"s "auxSDPLine()" will be NULL initially,
    // and we need to start reading data from our file until this changes.
    fDummyRTPSink = rtpSink;

    // Start reading the file:
    fDummyRTPSink->startPlaying(*inputSource, afterPlayingDummy, this);

    // Check whether the sink's 'auxSDPLine()' is ready:
    checkForAuxSDPLine(this);
  }

  envir().taskScheduler().doEventLoop(&fDoneFlag);

  return fAuxSDPLine;
}

FramedSource* H264VideoStreamServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
  estBitrate = 500; // kb
  megamol::remotecontrol::View3D_MRC *parent = (megamol::remotecontrol::View3D_MRC*)this->parent;
  return H264VideoStreamFramer::createNew(envir(), parent->h264FramedSource);
}

RTPSink* H264VideoStreamServerMediaSubsession::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
  return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}

FramedSource.cpp

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)
{

    x264_param_default_preset(&param, "veryfast", "zerolatency");
    param.i_threads = 1;
    param.i_width = 1024;
    param.i_height = 768;
    param.i_fps_num = 30;
    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");

    param.i_log_level = X264_LOG_ERROR;

    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, 1024, 768);

    convertCtx = sws_getContext(1024, 768, PIX_FMT_RGBA, 1024, 768, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    eventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0);
}

H264FramedSource::~H264FramedSource()
{
    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 = 1024*4;
    sws_scale(convertCtx, &surfaceData, &srcstride,0, 768, 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 = nullptr;

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

Соответствующая часть RTSP-сервера Therad

  RTSPServer* rtspServer = RTSPServer::createNew(*(parent->env), 8554, NULL);
  if (rtspServer == NULL) {
    *(parent->env) << "Failed to create RTSP server: " << (parent->env)->getResultMsg() << "\n";
    exit(1);
  }
  char const* streamName = "Stream";
  parent->h264FramedSource = H264FramedSource::createNew(*(parent->env), 0, 0);
  H264VideoStreamServerMediaSubsession *h264VideoStreamServerMediaSubsession = H264VideoStreamServerMediaSubsession::createNew(*(parent->env), true);
  h264VideoStreamServerMediaSubsession->parent = parent;
  sms->addSubsession(h264VideoStreamServerMediaSubsession);
  rtspServer->addServerMediaSession(sms);

  parent->env->taskScheduler().doEventLoop(); // does not return

Как только соединение существует, цикл рендеринга вызывает

h264FramedSource->AddToBuffer(videoData, 1024*768*4);

person user2660369    schedule 17.10.2013    source источник


Ответы (1)


Первое, что вам нужно сделать, это написать обертку вокруг x264 Encoder, которую вы можете использовать для кодирования данных RGB с хорошим заданным интерфейсом. следующий урок даст вам представление о том, как это сделать. Я использовал этот класс для кодирования кадра RAW BGR, который я получал из моего захвата opencv.

x264Encoder.h

#ifdef __cplusplus
#define __STDINT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
#include <iostream>
#include <concurrent_queue.h>
#include "opencv2\opencv.hpp"
#include <queue>
#include <stdint.h>
extern "C" {
#include "x264\x264.h"
}

class x264Encoder
{
public:
    x264Encoder(void);
    ~x264Encoder(void);

public:
    void initilize();
    void unInitilize();
    void encodeFrame(cv::Mat& image);
    bool isNalsAvailableInOutputQueue();
    x264_nal_t getNalUnit();
private:
    // Use this context to convert your BGR Image to YUV image since x264 do not support RGB input
    SwsContext* convertContext;
    std::queue<x264_nal_t> outputQueue;
    x264_param_t parameters;
    x264_picture_t picture_in,picture_out;
    x264_t* encoder;
};

x264Encoder.cpp

#include "x264Encoder.h"


x264Encoder::x264Encoder(void)
{

}


x264Encoder::~x264Encoder(void)
{

}

void x264Encoder::initilize()
{
    x264_param_default_preset(&parameters, "veryfast", "zerolatency");
    parameters.i_log_level = X264_LOG_INFO;
    parameters.i_threads = 1;
    parameters.i_width = 640;
    parameters.i_height = 480;
    parameters.i_fps_num = 25;
    parameters.i_fps_den = 1;
    parameters.i_keyint_max = 25;
    parameters.b_intra_refresh = 1;
    parameters.rc.i_rc_method = X264_RC_CRF;
    parameters.rc.i_vbv_buffer_size = 1000000;
    parameters.rc.i_vbv_max_bitrate = 90000;
    parameters.rc.f_rf_constant = 25;
    parameters.rc.f_rf_constant_max = 35;
    parameters.i_sps_id = 7;
    // the following two value you should keep 1
    parameters.b_repeat_headers = 1;    // to get header before every I-Frame
    parameters.b_annexb = 1; // put start code in front of nal. we will remove start code later
    x264_param_apply_profile(&parameters, "baseline");

    encoder = x264_encoder_open(&parameters);
    x264_picture_alloc(&picture_in, X264_CSP_I420, parameters.i_width, parameters.i_height);
    picture_in.i_type = X264_TYPE_AUTO;
    picture_in.img.i_csp = X264_CSP_I420;
    // i have initilized my color space converter for BGR24 to YUV420 because my opencv video capture gives BGR24 image. You can initilize according to your input pixelFormat
    convertContext = sws_getContext(parameters.i_width,parameters.i_height, PIX_FMT_BGR24, parameters.i_width,parameters.i_height,PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}

void x264Encoder::unInitilize()
{
    x264_encoder_close(encoder);
    sws_freeContext(convertContext);
}

void x264Encoder::encodeFrame(cv::Mat& image)
{
    int srcStride = parameters.i_width * 3;
    sws_scale(convertContext, &(image.data), &srcStride, 0, parameters.i_height, picture_in.img.plane, picture_in.img.i_stride);
    x264_nal_t* nals ;
    int i_nals = 0;
    int frameSize = -1;

    frameSize = x264_encoder_encode(encoder, &nals, &i_nals, &picture_in, &picture_out);
    if(frameSize > 0)
    {
        for(int i = 0; i< i_nals; i++)
        {
            outputQueue.push(nals[i]);
        }
    }
}

bool x264Encoder::isNalsAvailableInOutputQueue()
{
    if(outputQueue.empty() == true)
    {
        return false;
    }
    else
    {
        return true;
    }
}

x264_nal_t x264Encoder::getNalUnit()
{
    x264_nal_t nal;
    nal = outputQueue.front();
    outputQueue.pop();
    return nal;
}

Теперь у нас есть кодировщик, который сделает изображение BGR и закодирует его. Мой кодировщик закодирует кадр и поместит все выходные данные в очередь вывода, которая будет транслироваться Live555. Чтобы реализовать источник живого видео, вам необходимо создать два класса, которые будут подклассами (подкласс OnDemandServerMediaSubsession и другой FramedSource). в обоих есть библиотека мультимедиа live555. Этот класс также будет обслуживать данные более чем одному клиенту.
Чтобы создать подкласс этих двух классов, вы можете сослаться на следующие классы.

H264LiveServerMediaSession.h (подкласс OnDemandServerMediaSubsession)

#include "liveMedia.hh"
#include "OnDemandServerMediaSubsession.hh"
#include "LiveSourceWithx264.h"

class H264LiveServerMediaSession:public OnDemandServerMediaSubsession
{
public:
    static H264LiveServerMediaSession* createNew(UsageEnvironment& env, bool reuseFirstSource);
    void checkForAuxSDPLine1();
    void afterPlayingDummy1();
protected:
    H264LiveServerMediaSession(UsageEnvironment& env, bool reuseFirstSource);
    virtual ~H264LiveServerMediaSession(void);
    void setDoneFlag() { fDoneFlag = ~0; }
protected:
    virtual char const* getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource);
    virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
    virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
private:
    char* fAuxSDPLine;
    char fDoneFlag;
    RTPSink* fDummySink;
};  

H264LiveServerMediaSession.cpp

#include "H264LiveServerMediaSession.h"


H264LiveServerMediaSession* H264LiveServerMediaSession::createNew(UsageEnvironment& env, bool reuseFirstSource)
{
    return new H264LiveServerMediaSession(env, reuseFirstSource);
}

H264LiveServerMediaSession::H264LiveServerMediaSession(UsageEnvironment& env, bool reuseFirstSource):OnDemandServerMediaSubsession(env,reuseFirstSource),fAuxSDPLine(NULL), fDoneFlag(0), fDummySink(NULL)
{

}


H264LiveServerMediaSession::~H264LiveServerMediaSession(void)
{
    delete[] fAuxSDPLine;
}


static void afterPlayingDummy(void* clientData)
{
    H264LiveServerMediaSession *session = (H264LiveServerMediaSession*)clientData;
    session->afterPlayingDummy1();
}

void H264LiveServerMediaSession::afterPlayingDummy1()
{
    envir().taskScheduler().unscheduleDelayedTask(nextTask());
    setDoneFlag();
}

static void checkForAuxSDPLine(void* clientData)
{
    H264LiveServerMediaSession* session = (H264LiveServerMediaSession*)clientData;
    session->checkForAuxSDPLine1();
}

void H264LiveServerMediaSession::checkForAuxSDPLine1()
{
    char const* dasl;
    if(fAuxSDPLine != NULL)
    {
        setDoneFlag();
    }
    else if(fDummySink != NULL && (dasl = fDummySink->auxSDPLine()) != NULL)
    {
        fAuxSDPLine = strDup(dasl);
        fDummySink = NULL;
        setDoneFlag();
    }
    else
    {
        int uSecsDelay = 100000;
        nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsDelay, (TaskFunc*)checkForAuxSDPLine, this);
    }
}

char const* H264LiveServerMediaSession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource)
{
    if(fAuxSDPLine != NULL) return fAuxSDPLine;
    if(fDummySink == NULL)
    {
        fDummySink = rtpSink;
        fDummySink->startPlaying(*inputSource, afterPlayingDummy, this);
        checkForAuxSDPLine(this);
    }

    envir().taskScheduler().doEventLoop(&fDoneFlag);
    return fAuxSDPLine;
}

FramedSource* H264LiveServerMediaSession::createNewStreamSource(unsigned clientSessionID, unsigned& estBitRate)
{
    // Based on encoder configuration i kept it 90000
    estBitRate = 90000;
    LiveSourceWithx264 *source = LiveSourceWithx264::createNew(envir());
    // are you trying to keep the reference of the source somewhere? you shouldn't.  
    // Live555 will create and delete this class object many times. if you store it somewhere  
    // you will get memory access violation. instead you should configure you source to always read from your data source
    return H264VideoStreamDiscreteFramer::createNew(envir(),source);
}

RTPSink* H264LiveServerMediaSession::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource)
{
    return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}  

Теперь нам нужно создать подкласс класса FramedSource, который есть в LiveMedia. Для модели вы можете обратиться к DeviceSource.cpp в библиотеке live555. Ниже показано, как я это сделал.
LiveSourceWithx264.h

#include <queue>
#include "x264Encoder.h"
#include "opencv2\opencv.hpp"

class LiveSourceWithx264:public FramedSource
{
public:
    static LiveSourceWithx264* createNew(UsageEnvironment& env);
    static EventTriggerId eventTriggerId;
protected:
    LiveSourceWithx264(UsageEnvironment& env);
    virtual ~LiveSourceWithx264(void);
private:
    virtual void doGetNextFrame();
    static void deliverFrame0(void* clientData);
    void deliverFrame();
    void encodeNewFrame();
    static unsigned referenceCount;
    std::queue<x264_nal_t> nalQueue;
    timeval currentTime;
    // videoCaptureDevice is my BGR data source. You can have according to your need
    cv::VideoCapture videoCaptureDevice;
    cv::Mat rawImage;
    // Remember the x264 encoder wrapper we wrote in the start
    x264Encoder *encoder;
}; 

LiveSourceWithx264.cpp

#include "LiveSourceWithx264.h"


LiveSourceWithx264* LiveSourceWithx264::createNew(UsageEnvironment& env)
{
    return new LiveSourceWithx264(env);
}

EventTriggerId LiveSourceWithx264::eventTriggerId = 0;

unsigned LiveSourceWithx264::referenceCount = 0;

LiveSourceWithx264::LiveSourceWithx264(UsageEnvironment& env):FramedSource(env)
{
    if(referenceCount == 0)
    {

    }
    ++referenceCount;
    videoCaptureDevice.open(0);
    encoder = new x264Encoder();
    encoder->initilize();
    if(eventTriggerId == 0)
    {
        eventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0);
    }
}


LiveSourceWithx264::~LiveSourceWithx264(void)
{
    --referenceCount;
    videoCaptureDevice.release();
    encoder->unInitilize();
    envir().taskScheduler().deleteEventTrigger(eventTriggerId);
    eventTriggerId = 0;
}

void LiveSourceWithx264::encodeNewFrame()
{
        rawImage.data = NULL;
        while(rawImage.data == NULL)
        {
            videoCaptureDevice >> rawImage;
            cv::waitKey(100);
        }
        // Got new image to stream
        assert(rawImage.data != NULL);
        encoder->encodeFrame(rawImage);
        // Take all nals from encoder output queue to our input queue
        while(encoder->isNalsAvailableInOutputQueue() == true)
        {
            x264_nal_t nal = encoder->getNalUnit();
            nalQueue.push(nal);
        }
}

void LiveSourceWithx264::deliverFrame0(void* clientData)
{
    ((LiveSourceWithx264*)clientData)->deliverFrame();
}

void LiveSourceWithx264::doGetNextFrame()
{
    if(nalQueue.empty() == true)
    {
        encodeNewFrame();
        gettimeofday(&currentTime,NULL);
        deliverFrame();
    }
    else
    {
        deliverFrame();
    }
}

void LiveSourceWithx264::deliverFrame()
{
    if(!isCurrentlyAwaitingData()) return;
    x264_nal_t nal = nalQueue.front();
    nalQueue.pop();
    assert(nal.p_payload != NULL);
    // You need to remove the start code which is there in front of every nal unit.  
    // the start code might be 0x00000001 or 0x000001. so detect it and remove it. pass remaining data to live555    
    int trancate = 0;
    if (nal.i_payload >= 4 && nal.p_payload[0] == 0 && nal.p_payload[1] == 0 && nal.p_payload[2] == 0 && nal.p_payload[3] == 1 )
    {
        trancate = 4;
    }
    else
    {
        if(nal.i_payload >= 3 && nal.p_payload[0] == 0 && nal.p_payload[1] == 0 && nal.p_payload[2] == 1 )
        {
            trancate = 3;
        }
    }

    if(nal.i_payload-trancate > fMaxSize)
    {
        fFrameSize = fMaxSize;
        fNumTruncatedBytes = nal.i_payload-trancate - fMaxSize;
    }
    else
    {
        fFrameSize = nal.i_payload-trancate;
    }
    fPresentationTime = currentTime;
    memmove(fTo,nal.p_payload+trancate,fFrameSize);
    FramedSource::afterGetting(this);
}  

Теперь мы закончили с реализацией классов. Теперь, чтобы выполнить настройку потоковой передачи, вы можете следовать примеру testOnDemandRTSPServer.cpp. Вот мой основной, где я сделал настройку

#include <iostream>
#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>
#include "H264LiveServerMediaSession.h"
#include "opencv2\opencv.hpp"
#include "x264Encoder.h" 
int main(int argc, char* argv[])
{
    TaskScheduler* taskSchedular = BasicTaskScheduler::createNew();
    BasicUsageEnvironment* usageEnvironment = BasicUsageEnvironment::createNew(*taskSchedular);
    RTSPServer* rtspServer = RTSPServer::createNew(*usageEnvironment, 8554, NULL);
    if(rtspServer == NULL)
    {
        *usageEnvironment << "Failed to create rtsp server ::" << usageEnvironment->getResultMsg() <<"\n";
        exit(1);
    }

    std::string streamName = "usb1";
    ServerMediaSession* sms = ServerMediaSession::createNew(*usageEnvironment, streamName.c_str(), streamName.c_str(), "Live H264 Stream");
    H264LiveServerMediaSession *liveSubSession = H264LiveServerMediaSession::createNew(*usageEnvironment, true);
    sms->addSubsession(liveSubSession);
    rtspServer->addServerMediaSession(sms);
    char* url = rtspServer->rtspURL(sms);
    *usageEnvironment << "Play the stream using url "<<url << "\n";
    delete[] url;
    taskSchedular->doEventLoop();
    return 0;
}

и у вас есть URL для вашего LiveSource. У меня была для моей USB-камеры :)

person bhawesh    schedule 28.01.2014
comment
Потрясающая работа по сборке целого примера. Я уже несколько дней борюсь с этой же проблемой, и мне определенно помогает что-то посмотреть. Однако код нуждался в некотором редактировании для фактической компиляции (заголовки, защита включения и т. д.), и на самом деле это не работает для меня. Как только я направляю экземпляр VLC на напечатанный URL-адрес, я вижу инициализацию камеры и некоторую отладку X264 в консоли. Я также вижу одно искаженное изображение, полученное VLC, но после этого ничего не происходит, пока я не остановлю VLC. Захват камеры OpenCV отлично работает для меня в моем собственном приложении. - person Jan Ekholm; 02.04.2014
comment
Кроме того, почему вы устанавливаете параметры. b_annexb = 1; а затем удалить добавленный заголовок? Почему бы просто не установить его на 0? - person Jan Ekholm; 02.04.2014
comment
установка параметра .b_annexb = 0 заменит только начальный код длиной полезной нагрузки, которая будет составлять 4 байта. так что не имеет значения, что вы установили. позже вам придется обрезать 4 байта, прежде чем перейти к Live555..... также вы можете оптимизировать код по-своему. это просто пример использования Live555. я не могу опубликовать свой производственный код здесь ... :) хотя этот образец работает для меня без каких-либо проблем - person bhawesh; 02.04.2014
comment
Спасибо за разъяснения. В вашем примере для меня передается одно искаженное изображение, но, возможно, это связано с OSX. Live555 кажется смехотворно сложным для работы и использует множество сомнительных практик C++. :) Здесь много движущихся частей, чтобы работать одновременно. - person Jan Ekholm; 02.04.2014
comment
Добавление parameters.b_annexb = 1 и удаление заголовков дает мне одно изображение в VLC в моем собственном приложении. Он не искажен или имеет небольшую ошибку только на одном краю. Но есть только один образ, больше ничего не меняется. Возможно, поток застревает в фиктивной раковине или что-то в этом роде... - person Jan Ekholm; 02.04.2014
comment
вы уверены, что ваш захват openCV работает правильно, потому что у меня была та же проблема, и, наконец, оказалось, что моя потоковая передача и кодирование были в порядке, но проблема была в захвате opencv, где после первого захвата opencv не захватывал ничего, но снова давал одно и то же изображение и опять таки - person bhawesh; 02.04.2014
comment
Я думаю, что это нормально, так как когда я провел простой тест только для OpenCV, который просто показывал захваченное изображение в окне OpenCV, все было в порядке. У меня также есть декодер X264 на уровне отладки, и значения, которые он показывает, различаются для каждого кадра. Размер закодированного кадра тоже разный, он совсем маленький, когда ничего не происходит и скачет, если я делаю какие-то резкие движения перед камерой. VLC также жалуется на задержку изображений. Пример приложения openRTSP, похоже, сохраняет файл видео-H264-1: последовательность JVT NAL, видео H.264, базовый уровень @ L 52. - person Jan Ekholm; 02.04.2014
comment
Хм, я попытался добавить окно OpenCV в свое приложение, и оно также все время показывает одно и то же изображение. Не уверен, что окно просто не работает в моем контексте или кадр действительно все время один и тот же. Вы имели в виду, что открытие камеры во второй раз давало все время одно и то же изображение? Что вы сделали в вашем случае, чтобы решить эту проблему? - person Jan Ekholm; 02.04.2014
comment
трудно сказать о вашей проблеме, не глядя на ваш код. Вы должны опубликовать свой код с вашим вопросом. - person bhawesh; 02.04.2014
comment
Конечно. В основном мне было любопытно, почему ваш VideoCapture больше не обновляется. Однако, если я делаю cv::namedWindow() и показываю там каждый кадр, кадр, показанный в этом окне, отличается от единственного кадра, который передается в потоковом режиме, поэтому я думаю, что захват работает как надо. Если я не смогу понять это, мне придется сократить его до минимального случая и опубликовать. Спасибо за помощь, я очень ценю это. - person Jan Ekholm; 03.04.2014
comment
Хорошо, после попытки openRTSP кажется, что поток отлично сохраняется, и после переименования файла во что-то.h264 VLC может открыть его и воспроизвести. Скорость слишком высока, так что теперь я предполагаю, что мое время презентации или частота кадров каким-то образом нарушены. Но это означает, что анкодирование и потоковая передача действительно работают, просто где-то ошибка. :) - person Jan Ekholm; 03.04.2014
comment
Ожидается, что воспроизведение видео будет быстрым, потому что .h264 не является контейнером, и вы не можете добавить время pts в этот файл. для правильного воспроизведения вы должны поместить поток в контейнер, скажем, .mp4 или .mkv с временем pts. - person bhawesh; 03.04.2014
comment
Я так и предполагал. Но видео хоть есть. Увеличивая сетевое кэширование в VLC, мне удается запустить прерывистое видео, но оно задерживается на 1 секунду или около того. В идеале мне нужно гораздо меньше, чем это. Но похоже, что потоковая передача теперь работает более или менее с тем кодом, который у вас был. Ваш автономный код также работает с большим буфером VLC, но также задерживается на 1 с+ из-за буферизации. Итак, сейчас речь идет о попытке снизить задержку. - person Jan Ekholm; 03.04.2014
comment
Спасибо за код, я уже некоторое время борюсь с отправкой кадров H264. Однако ваш код не решает мою проблему: как в моем, так и в вашем коде кадры усекаются из-за небольшого размера буфера. fMaxSize, кажется, сильно меняется - как это делает live555, просто догадайтесь? есть кадры через раз, которые приходится обрезать, в результате рендеринга на стороне клиента не хватает данных и появляются серьезные артефакты. Как вы справляетесь с этим? - person iko79; 12.01.2017
comment
@bhawesh Я знаю, что это немного старый вопрос, но ваше решение не похоже на одноадресное. Я все еще могу получить доступ к потоку с нескольких устройств. Не могли бы вы уточнить, как преобразовать этот код в одноадресную рассылку? - person ktb92677; 19.03.2019