2012-12-13 6 views
7

Ho cercato di scrivere una classe che deriva da FramedSource in Live555 che mi consentirà di trasmettere dati live dalla mia applicazione D3D9 a un MP4 o simile .Come scrivere un Live755 FramedSource per consentirmi di trasmettere live H.264

Quello che faccio ogni frame è catturare il backbuffer nella memoria di sistema come una trama, quindi convertirlo da RGB -> YUV420P, quindi codificarlo usando x264, quindi idealmente passare i pacchetti NAL a Live555. Ho creato una classe chiamata H264FramedSource derivata da FramedSource in pratica copiando il file DeviceSource. Invece che l'input è un file di input, ho creato un pacchetto NAL che aggiorno ogni frame.

Sono abbastanza nuovo per i codec e lo streaming, quindi potrei fare tutto completamente sbagliato. In ogni doGetNextFrame() dovrei afferrare il pacchetto NAL e fare qualcosa di simile a

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

Suppongo che il carico utile è il mio frame di dati in byte? Se qualcuno ha un esempio di una classe derivata da FramedSource che potrebbe almeno essere vicino a quello che sto cercando di fare mi piacerebbe vederlo, questo è tutto nuovo per me e un po 'complicato per capire cosa sta succedendo. La documentazione di Live555 è più o meno il codice stesso che non mi rende esattamente facile capirlo.

+1

non sono sicuro se sarà risolvere il problema o no, ma di tenere la mente, i pacchetti NAL non sono 1-1 con i frame, è possibile ottenere 0-molti pacchetti NAL da una singola codifica frame. – jeremy

+0

Grazie per la risposta, ero abbastanza sicuro che fosse il caso. In che modo la FramedSource di un utente è responsabile? – Garviel

+1

Hai considerato l'utilizzo di H264VideoStreamDiscreteFramer invece di H264VideoStreamFramer? Farò un po 'più di ricerche e vedrò se qualcosa si apre. (vedi http://comments.gmane.org/gmane.comp.multimedia.live555.devel/8450) – jeremy

risposta

12

Ok, ho finalmente avuto un po 'di tempo da dedicare a questo e ho funzionato! Sono sicuro che ci saranno altri che supplicheranno di sapere come farlo, eccolo qui.

Avrete bisogno del vostro FramedSource per prendere ogni frame, codificarlo e prepararlo per lo streaming, presto fornirò un po 'del codice sorgente.

In sostanza, lanciare FramedSource in H264VideoStreamDiscreteFramer, quindi inserirlo nell'H264RTPSink. Qualcosa di simile

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

Ora nel tuo ciclo di rendering principale, gettare sopra la vostra backbuffer che hai salvato nella memoria di sistema al vostro FramedSource in modo che possa essere codificato ecc Per ulteriori informazioni su come configurare il controllo di roba di codifica questa risposta How does one encode a series of images into H264 using the x264 C API?

La mia implementazione è molto in uno stato di hacky e deve ancora essere ottimizzata, la mia applicazione d3d gira a circa 15fps a causa della codifica, ahi, quindi dovrò esaminarlo. Ma a tutti gli effetti questa domanda StackOverflow ha una risposta, perché ero principalmente dopo come lo streaming. Spero che questo aiuti le altre persone.

quanto riguarda il mio FramedSource sembra un po 'simile a questa

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, e per coloro che vogliono sapere che cosa la mia coda simultaneo è, qui è, e funziona brillantemente http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html

Godetevi e buona fortuna!

+0

Grazie per aver condiviso – user718146

+0

ciao Garviel, nella tua H264FramedSource nel caso in cui il carico utile sia maggiore del buffer di destinazione, imposta SOLO il valore fNumTruncatedBytes, il resto del carico di riproduzione verrebbe perso? se no, potresti dirmi il meccanismo sotto il cofano – user718146

+0

Ciao, i dati sono semplicemente persi. Se ricordo bene, non devi eseguire il troncamento. Tutto dipende da cosa è accettabile nella tua applicazione/configurazione. Nel mio caso stavo trasmettendo in streaming su una rete e se non l'avessi limitato avrei avuto un sacco di artefatti visivi e incoerenza nella latenza. È passato un po 'di tempo da quando ho dovuto guardare a questo perché non sono andato oltre il prototipo. – Garviel

2

Il metodo deliverFrame manca il seguente controllo al suo inizio:

if (!isCurrentlyAwaitingData()) return;  

vedere DeviceSource.cpp in DIRETTA

+0

Hi koraxkorakos, in realtà avevo un metodo esterno alla classe che chiamava qualcosa sulla falsariga di isLive555Waiting. Ricordo la sua esistenza, ma non l'attuazione mi dispiace. – Garviel

Problemi correlati