static void* EncoderThread( void *obj ) { sout_stream_sys_t *p_sys = (sout_stream_sys_t*)obj; sout_stream_id_sys_t *id = p_sys->id_video; picture_t *p_pic = NULL; int canc = vlc_savecancel (); block_t *p_block = NULL; vlc_mutex_lock( &p_sys->lock_out ); for( ;; ) { while( !p_sys->b_abort && (p_pic = picture_fifo_Pop( p_sys->pp_pics )) == NULL ) vlc_cond_wait( &p_sys->cond, &p_sys->lock_out ); if( p_pic ) { /* release lock while encoding */ vlc_mutex_unlock( &p_sys->lock_out ); p_block = id->p_encoder->pf_encode_video( id->p_encoder, p_pic ); picture_Release( p_pic ); vlc_mutex_lock( &p_sys->lock_out ); block_ChainAppend( &p_sys->p_buffers, p_block ); } if( p_sys->b_abort ) break; } /*Encode what we have in the buffer on closing*/ while( (p_pic = picture_fifo_Pop( p_sys->pp_pics )) != NULL ) { p_block = id->p_encoder->pf_encode_video( id->p_encoder, p_pic ); picture_Release( p_pic ); block_ChainAppend( &p_sys->p_buffers, p_block ); } /*Now flush encoder*/ do { p_block = id->p_encoder->pf_encode_video(id->p_encoder, NULL ); block_ChainAppend( &p_sys->p_buffers, p_block ); } while( p_block ); vlc_mutex_unlock( &p_sys->lock_out ); vlc_restorecancel (canc); return NULL; }
/** * @brief VLC flush callback * @param intf */ static void y4m_flush(filter_t* intf) { filter_sys_t* sys = intf->p_sys; msg_Info(intf, "flush: enter: buffers=%d:%d:%d", sys->bufferedIn, sys->bufferedOut, sys->minBuffered); // flush what we can, there can still be frames outstanding // after the flush so the counts need to be updated // block the input thread while flushing its fifo. // vlc_mutex_lock(&sys->inputMutex); // picture_t* pic; // while ((pic = picture_fifo_Pop(sys->inputFifo))) // { // picture_Release(pic); // sys->bufferedIn--; // sys->bufferedOut -= sys->bufferRatio; // } // vlc_mutex_unlock(&sys->inputMutex); picture_t* pic; while ((pic = picture_fifo_Pop(sys->outputFifo))) { picture_Release(pic); sys->bufferedOut--; } // check our accounting, if it goes below zero there is race somewhere if (sys->bufferedOut < 0) { msg_Err(intf, "flush: race condition on output count"); sys->bufferedOut = 0; } if (sys->bufferedIn < 0) { msg_Err(intf, "flush: race condition on input count"); sys->bufferedIn = 0; } sys->minBuffered = sys->bufferedOut; sys->lastDate = 0; msg_Info(intf, "flush: exit: buffers=%d:%d:%d", sys->bufferedIn, sys->bufferedOut, sys->minBuffered); }
/** * @brief Thread writing frames to the subprocess */ static void* inputThread(void* userData) { filter_t* intf = (filter_t*)userData; filter_sys_t* sys = intf->p_sys; msg_Info(intf, "inputThread: enter"); while (true) { picture_t* pic; // acquire a frame pushed from y4m_filter() vlc_mutex_lock(&sys->inputMutex); mutex_cleanup_push(&sys->inputMutex); while (NULL == (pic = picture_fifo_Pop(sys->inputFifo))) { //msg_Info(intf, "inputThread: wait picture"); vlc_cond_wait(&sys->inputCond, &sys->inputMutex); } sys->bufferedIn--; vlc_mutex_unlock(&sys->inputMutex); vlc_cleanup_pop(); //msg_Info(intf, "inputThread: write picture"); if (1 != writeY4mFrame(intf, pic, sys->stdin)) { msg_Err(intf, "inputThread: Failed to write y4m frame"); break; } picture_Release(pic); } msg_Info(intf, "inputThread: exit"); sys->threadExit = true; return userData; }
static void* EncoderThread( void *obj ) { sout_stream_sys_t *p_sys = (sout_stream_sys_t*)obj; sout_stream_id_t *id = p_sys->id_video; picture_t *p_pic; int canc = vlc_savecancel (); for( ;; ) { block_t *p_block; vlc_mutex_lock( &p_sys->lock_out ); while( !p_sys->b_abort && (p_pic = picture_fifo_Pop( p_sys->pp_pics )) == NULL ) vlc_cond_wait( &p_sys->cond, &p_sys->lock_out ); if( p_sys->b_abort ) { vlc_mutex_unlock( &p_sys->lock_out ); break; } vlc_mutex_unlock( &p_sys->lock_out ); p_block = id->p_encoder->pf_encode_video( id->p_encoder, p_pic ); vlc_mutex_lock( &p_sys->lock_out ); block_ChainAppend( &p_sys->p_buffers, p_block ); vlc_mutex_unlock( &p_sys->lock_out ); picture_Release( p_pic ); } block_ChainRelease( p_sys->p_buffers ); vlc_restorecancel (canc); return NULL; }
static picture_t *ImageRead( image_handler_t *p_image, block_t *p_block, const video_format_t *p_fmt_in, video_format_t *p_fmt_out ) { picture_t *p_pic = NULL; /* Check if we can reuse the current decoder */ if( p_image->p_dec && p_image->p_dec->fmt_in.i_codec != p_fmt_in->i_chroma ) { DeleteDecoder( p_image->p_dec ); p_image->p_dec = 0; } /* Start a decoder */ if( !p_image->p_dec ) { p_image->p_dec = CreateDecoder( p_image->p_parent, p_fmt_in ); if( !p_image->p_dec ) { block_Release(p_block); return NULL; } if( p_image->p_dec->fmt_out.i_cat != VIDEO_ES ) { DeleteDecoder( p_image->p_dec ); p_image->p_dec = NULL; block_Release(p_block); return NULL; } p_image->p_dec->pf_queue_video = ImageQueueVideo; p_image->p_dec->p_queue_ctx = p_image; } p_block->i_pts = p_block->i_dts = mdate(); int ret = p_image->p_dec->pf_decode( p_image->p_dec, p_block ); if( ret == VLCDEC_SUCCESS ) { /* Drain */ p_image->p_dec->pf_decode( p_image->p_dec, NULL ); p_pic = picture_fifo_Pop( p_image->outfifo ); unsigned lostcount = 0; picture_t *lostpic; while( ( lostpic = picture_fifo_Pop( p_image->outfifo ) ) != NULL ) { picture_Release( lostpic ); lostcount++; } if( lostcount > 0 ) msg_Warn( p_image->p_parent, "Image decoder output more than one " "picture (%d)", lostcount ); } if( p_pic == NULL ) { msg_Warn( p_image->p_parent, "no image decoded" ); return 0; } if( !p_fmt_out->i_chroma ) p_fmt_out->i_chroma = p_image->p_dec->fmt_out.video.i_chroma; if( !p_fmt_out->i_width && p_fmt_out->i_height ) p_fmt_out->i_width = (int64_t)p_image->p_dec->fmt_out.video.i_width * p_image->p_dec->fmt_out.video.i_sar_num * p_fmt_out->i_height / p_image->p_dec->fmt_out.video.i_height / p_image->p_dec->fmt_out.video.i_sar_den; if( !p_fmt_out->i_height && p_fmt_out->i_width ) p_fmt_out->i_height = (int64_t)p_image->p_dec->fmt_out.video.i_height * p_image->p_dec->fmt_out.video.i_sar_den * p_fmt_out->i_width / p_image->p_dec->fmt_out.video.i_width / p_image->p_dec->fmt_out.video.i_sar_num; if( !p_fmt_out->i_width ) p_fmt_out->i_width = p_image->p_dec->fmt_out.video.i_width; if( !p_fmt_out->i_height ) p_fmt_out->i_height = p_image->p_dec->fmt_out.video.i_height; if( !p_fmt_out->i_visible_width ) p_fmt_out->i_visible_width = p_fmt_out->i_width; if( !p_fmt_out->i_visible_height ) p_fmt_out->i_visible_height = p_fmt_out->i_height; /* Check if we need chroma conversion or resizing */ if( p_image->p_dec->fmt_out.video.i_chroma != p_fmt_out->i_chroma || p_image->p_dec->fmt_out.video.i_width != p_fmt_out->i_width || p_image->p_dec->fmt_out.video.i_height != p_fmt_out->i_height ) { if( p_image->p_filter ) if( p_image->p_filter->fmt_in.video.i_chroma != p_image->p_dec->fmt_out.video.i_chroma || p_image->p_filter->fmt_out.video.i_chroma != p_fmt_out->i_chroma ) { /* We need to restart a new filter */ DeleteFilter( p_image->p_filter ); p_image->p_filter = 0; } /* Start a filter */ if( !p_image->p_filter ) { p_image->p_filter = CreateFilter( p_image->p_parent, &p_image->p_dec->fmt_out, p_fmt_out ); if( !p_image->p_filter ) { picture_Release( p_pic ); return NULL; } } else { /* Filters should handle on-the-fly size changes */ p_image->p_filter->fmt_in = p_image->p_dec->fmt_out; p_image->p_filter->fmt_out = p_image->p_dec->fmt_out; p_image->p_filter->fmt_out.i_codec = p_fmt_out->i_chroma; p_image->p_filter->fmt_out.video = *p_fmt_out; } p_pic = p_image->p_filter->pf_video_filter( p_image->p_filter, p_pic ); video_format_Clean( p_fmt_out ); video_format_Copy( p_fmt_out, &p_image->p_filter->fmt_out.video ); } else { video_format_Clean( p_fmt_out ); video_format_Copy( p_fmt_out, &p_image->p_dec->fmt_out.video ); } return p_pic; }
/** * @brief VLC filter callback * @return picture(s) containing the filtered frames */ static picture_t* y4m_filter(filter_t* intf, picture_t* srcPic) { filter_sys_t* sys = intf->p_sys; //msg_Info(intf, ">>>> filter"); if (!srcPic) { //msg_Info(intf, ">>> filter: NULL INPUT"); return NULL; } // will be stored to sys->lastDate on return mtime_t currDate = srcPic->date; // if there was a problem with the subprocess then send back // the picture unmodified, at least we won't freeze up vlc if (sys->startFailed || sys->threadExit) goto ECHO_RETURN; // start subprocess and write the y4m header // fixme: this can go in open() if fmt_in matches the srcPic if (!sys->startFailed && !sys->childPid) { sys->startFailed = !startProcess(intf); if (sys->startFailed) goto ECHO_RETURN; if (0 >= writeY4mHeader(intf, srcPic, sys->stdin)) { msg_Err(intf, "writeY4mHeader failed: errno=%d %s", errno, strerror(errno)); stopProcess(intf); sys->startFailed = true; goto ECHO_RETURN; } } // // control the buffering level by monitoring the input/output fifos // // the input/output fifos are emptied/filled by input/output threads // in response to the subprocess reading/writing frames // // if the input fifo is empty, then probably the subprocess wants // more input, so we should buffer more input // // if the output fifo is empty, and the input fifo is not empty, then // probably the subprocess is about to write out a frame and we // can wait for it to arrive. in practice, most of the time there // is no waiting needed, unless the filter is too slow to keep up. // bool inputEmpty = true; bool outputEmpty = true; picture_t* tmp = picture_fifo_Peek(sys->inputFifo); if (tmp) { picture_Release(tmp); inputEmpty = false; } tmp = picture_fifo_Peek(sys->outputFifo); if (tmp) { picture_Release(tmp); outputEmpty = false; } // copy picture to input fifo, we can't use picture_Hold or else // the decoder or vout would run out of pictures in its pool picture_t* inPic = picture_NewFromFormat(&srcPic->format); picture_Copy(inPic, srcPic); picture_fifo_Push(sys->inputFifo, inPic); // signal input thread to wake up and write some more data out vlc_mutex_lock(&sys->inputMutex); sys->bufferedIn++; vlc_cond_signal(&sys->inputCond); vlc_mutex_unlock(&sys->inputMutex); // if echo is enabled, we're done // todo: there should be a limiter on the input buffering in case // the subprocess can't keep up if (sys->echo) goto ECHO_RETURN; // keeps track of the number of buffered output pictures, assumes // an integer ratio // fixme: needs modification to support non-integer ratios // and ratios < 1 sys->bufferedOut += sys->bufferRatio; // handle buffering if (outputEmpty && inputEmpty) { // we haven't supplied enough input, raise the minimum // level of buffer to keep and return sys->minBuffered += sys->bufferRatio; msg_Info(intf, "buffer more input: buffers:%d:%d:%d", sys->bufferedIn, sys->bufferedOut, sys->minBuffered); goto NULL_RETURN; } if (outputEmpty) waitForOutput(intf); // if we don't know what the frame interval is, make it 0 which // probably causes the next frames out to drop // note: this happens at least every time y4m_flush() is called // for example when seeking if (currDate <= sys->lastDate || sys->lastDate == 0) { //msg_Err(intf, "currDate <= lastDate"); //goto ECHO_RETURN; sys->lastDate = currDate; } // reference to first and last picture we are returning picture_t* first = NULL; picture_t* last = NULL; picture_t* pic; while( (pic = picture_fifo_Pop(sys->outputFifo)) ) { // do output setup when we see the first frame out from the filter, // it could have a different frame rate, chroma, size, etc than // the frame going in if (!sys->gotFirstOutput) { sys->gotFirstOutput = true; // get the in/out frame ratio by comparing frame rates float speed = ((float)srcPic->format.i_frame_rate_base * (float)pic->format.i_frame_rate) / ((float)srcPic->format.i_frame_rate * (float)pic->format.i_frame_rate_base); if (speed < 1.0) { msg_Err(intf, "frame rate reduction isn't supported yet"); } else if (speed > 1.0) { if (ceil(speed) != speed) msg_Err(intf, "frame rate change must be integer ratio"); sys->bufferRatio = speed; // initial ratio was 1.0, need to correct the number of buffered frames // now that we know what it is sys->bufferedOut *= sys->bufferRatio; sys->minBuffered *= sys->bufferRatio; } intf->fmt_out.video.i_frame_rate = pic->format.i_frame_rate; intf->fmt_out.video.i_frame_rate_base = pic->format.i_frame_rate_base; if (intf->fmt_out.video.i_chroma != pic->format.i_chroma) msg_Err(intf, "filter changed the chroma, expect corruption"); // this can't be changed after open, crashes the GLX vout //intf->fmt_out.i_codec = pic->format.i_chroma; //intf->fmt_out.video.i_chroma = pic->format.i_chroma; msg_Info(intf, "first output: buffers=%d:%d", sys->bufferedOut, sys->minBuffered); } sys->numFrames++; sys->bufferedOut--; // it seems filter_NewPicture is required now. however, // sometimes it returns null in which case it seems like // the best thing to do is dump frames picture_t* copy = first == NULL ? srcPic : filter_NewPicture(intf); if (!copy) { picture_Release(pic); // throw away frames // vlc already prints warning for this //msg_Err(intf, "filter_NewPicture returns null"); if (sys->bufferedOut < sys->minBuffered) break; else continue; } else { picture_CopyPixels(copy, pic); picture_Release(pic); pic = copy; } // the time per output frame interval is a fraction of the input frame time int frameTime = (currDate - sys->lastDate) / sys->bufferRatio; // the pts is whatever the current pts is minus any buffering // introduced by the filter pic->date = currDate - sys->bufferedOut*frameTime; // msg_Info(intf, "frame=%d buffered=%d:%d frameTime=%d ratio:%d:1 fin=%d:%d fout=%d:%d pts=%u", // sys->numFrames, sys->bufferedOut, sys->minBuffered, // frameTime, sys->bufferRatio, // srcPic->format.i_frame_rate, srcPic->format.i_frame_rate_base, // pic->format.i_frame_rate, pic->format.i_frame_rate_base, // (unsigned int)pic->date); if (last) last->p_next = pic; else first = pic; last = pic; // if we read too many frames on this iteration, on the next // one we might not have any frames available which would be // bad as vlc would think our intent was to drop frames // // if we stop reading before the output is completely empty, // there will always be some frames for the next iteration, // assuming the filter is fast enough to keep up if (sys->bufferedOut < sys->minBuffered) break; // if there is still some input buffer left, but the fifo is // empty, wait for next frame to arrive. otherwise we can // build too much input buffering if (sys->bufferedIn > 1) waitForOutput(intf); } if (!first) { // the buffer checks should prevent from getting here, but // just in case prevent leaking the input picture picture_Release(srcPic); sys->minBuffered++; } sys->lastDate = currDate; return first; ECHO_RETURN: sys->lastDate = currDate; //msg_Info(intf, "<<<< filter: ECHO"); return srcPic; NULL_RETURN: sys->lastDate = currDate; picture_Release(srcPic); //msg_Info(intf, "<<<< filter: NULL"); return NULL; }