c++ ffmpeg h.264 x264 live555

c++ - Cómo escribir un Live555 FramedSource para permitirme transmitir H.264 en vivo



ffmpeg x264 (2)

He intentado escribir una clase que se deriva de FramedSource en Live555 que me permitirá transmitir datos en vivo desde mi aplicación D3D9 a un MP4 o similar.

Lo que hago con cada fotograma es tomar el backbuffer en la memoria del sistema como una textura, luego convertirlo de RGB -> YUV420P, luego codificarlo usando x264, y luego idealmente pasar los paquetes NAL a Live555. Hice una clase llamada H264FramedSource que derivaba de FramedSource básicamente al copiar el archivo DeviceSource. En lugar de que la entrada sea un archivo de entrada, lo he convertido en un paquete NAL que actualizo cada cuadro.

Soy bastante nuevo para los códecs y la transmisión, por lo que podría estar haciendo todo completamente mal. En cada doGetNextFrame () debería tomar el paquete NAL y hacer algo como

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

Supongo que la carga útil es mi información de fotograma en bytes. Si alguien tiene un ejemplo de una clase que derivaron de FramedSource que al menos podría estar cerca de lo que estoy tratando de hacer, me encantaría verlo, todo esto es nuevo para mí y un poco difícil de entender lo que está sucediendo. La documentación de Live555 es más o menos el código en sí mismo, lo que no hace que sea muy fácil para mí descifrarlo.


El método deliverFrame carece de la siguiente verificación al inicio:

if (!isCurrentlyAwaitingData()) return;

ver DeviceSource.cpp en VIVO


Ok, finalmente tengo algo de tiempo para gastar en esto y lo tengo funcionando! Estoy seguro de que hay otros que suplicarán saber cómo hacerlo, así que aquí está.

Necesitará su propio FramedSource para tomar cada fotograma, codificarlo y prepararlo para la transmisión. Pronto proporcionaré algunos de los códigos fuente para esto.

Esencialmente arroje su FramedSource al H264VideoStreamDiscreteFramer, luego tírelo al H264RTPSink. Algo como esto

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

Ahora, en su bucle de renderización principal, transfiera su backbuffer que ha guardado a la memoria del sistema a su FramedSource para que pueda ser codificado, etc. Para más información sobre cómo configurar las cosas de codificación, mire esta respuesta. ¿Cómo se codifica una serie de imágenes en H264 usando la API x264 C?

Mi implementación está muy mal y todavía no se ha optimizado, mi aplicación d3d se ejecuta a unos 15 fps debido a la codificación, por lo que tendré que investigar esto. Pero a todos los efectos, esta pregunta de se responde porque, en su mayor parte, estaba después de cómo transmitirla. Espero que esto ayude a otras personas.

En cuanto a mi FramedSource se ve algo así como esto

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

Ah, y para aquellos que quieren saber cuál es mi cola concurrente, aquí está, y funciona brillantemente http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition- variables.html

¡Disfruta y buena suerte!