Search code examples
c++-cxms-media-foundation

Encode multiple frames with MediaTranscoder


I'm trying to convert a series of video frames, which came from a capturing device, from IYUV to H264 using Media Foundation in a Store App. MediaTranscoder works only for first frame then it shuts down and closes the MediaStreamSource. How can I make it to continuously ask for new frames to encode? Here is the relevant code:

int InitEncoder(const VideoCodec* inst) {

    auto encoderProps = VideoEncodingProperties::CreateUncompressed(MediaEncodingSubtypes::Iyuv, inst->width, inst->height);
    streamDescriptor_ = ref new VideoStreamDescriptor(encoderProps);
    mediaStreamSource_ = ref new MediaStreamSource(streamDescriptor_);

    mediaStreamSource_->Starting += 
    ref new TypedEventHandler<MediaStreamSource ^, MediaStreamSourceStartingEventArgs ^>
    ([=](MediaStreamSource ^source, MediaStreamSourceStartingEventArgs ^ args)
    {
        args->Request->GetDeferral()->Complete();
    });

    mediaStreamSource_->SwitchStreamsRequested += 
    ref new TypedEventHandler<MediaStreamSource ^, MediaStreamSourceSwitchStreamsRequestedEventArgs ^>
    ([=](MediaStreamSource ^source,     MediaStreamSourceSwitchStreamsRequestedEventArgs ^args)
    {
        OutputDebugString(L"Media stream source switch stream requested.\n");
    });

    mediaStreamSource_->Closed += 
    ref new TypedEventHandler<MediaStreamSource ^, MediaStreamSourceClosedEventArgs ^>
    ([=](MediaStreamSource ^source, MediaStreamSourceClosedEventArgs ^args)
    {
        OutputDebugString(L"Media stream source closed event, reason: ");
    });

    mediaStreamSource_->SampleRequested +=
    ref new TypedEventHandler<MediaStreamSource ^, MediaStreamSourceSampleRequestedEventArgs ^>
    ([=](MediaStreamSource ^source, MediaStreamSourceSampleRequestedEventArgs ^args){
    if (framesToEncode_.size() > 0) {
        FeedOneSample(args->Request);        
    }
    else {
        sampleRequest_ = args->Request;
        args->Request->ReportSampleProgress(1);
        isSampleRequestPending_ = true;
    }
    });

    mediaTranscoder_ = ref new MediaTranscoder();
    mediaTranscoder_->VideoProcessingAlgorithm = MediaVideoProcessingAlgorithm::Default;
    mediaTranscoder_->HardwareAccelerationEnabled = true;    

    encodedStream_ = ref new InMemoryRandomAccessStream();

    h264EncodingProfile_ = MediaEncodingProfile::CreateMp4(VideoEncodingQuality::HD720p);
    h264EncodingProfile_->Audio = nullptr;
    h264EncodingProfile_->Container = nullptr;

    auto prepareTranscodeResultTask = mediaTranscoder_->PrepareMediaStreamSourceTranscodeAsync(
    mediaStreamSource_,
    encodedStream_,
    h264EncodingProfile_);

    auto prepareTranscodeResultContinuationTask = create_task(prepareTranscodeResultTask).then(
    [=](PrepareTranscodeResult ^prepTransResult) {
    prepareTranscodeResult_ = prepTransResult;
    if (!prepTransResult->CanTranscode) {
        TranscodeFailureReason failureReason = TranscodeFailureReason::None;
        failureReason = prepTransResult->FailureReason;
    }
    });
    prepareTranscodeResultContinuationTask.then([this]()
    {  
        if (prepareTranscodeResult_->CanTranscode) {
            inited_ = true;
        }
        return 0;
    });

    return 0;
}

int EncodeFrame(const VideoFrame& frame) {

    if (!inited_)
    {
        return -1;
    }

    framesToEncode_.push_back(newFrame);

    if (framesToEncode_.size() == 1 && !transcodeStarted_)
    {
        auto transcodeOp = prepareTranscodeResult_->TranscodeAsync();
        OnTranscodeAsync(transcodeOp);
        transcodeStarted_ = true;
    }
    if (isSampleRequestPending_ && framesToEncode_.size() > 0)
    {
        FeedOneSample(sampleRequest_);
        isSampleRequestPending_ = false;
    }

    return 0;
}

void OnTranscodeAsync(
IAsyncActionWithProgress<double> ^actionProgress) {
    auto transcodeContinuation = create_task(actionProgress);
    transcodeContinuation.then([=]() {
        OutputDebugString(L"Transcoding frame complete.\n");
        OutputDebugString(L"Size of encoded stream is: ");
        OutputDebugString(encodedStream_->Size.ToString()->Data());
        OutputDebugString(L"\n");
    });
}

void FeedOneSample(MediaStreamSourceSampleRequest ^sampleRequest)
{
    const VideoFrame* frame = framesToEncode_.front();
    framesToEncode_.pop_front();
    sampleRequest->Sample = TransformToMediaStreamSample(frame);
    sampleRequest->GetDeferral()->Complete();
}

MediaStreamSample^ TransformToMediaStreamSample(const VideoFrame* frame)
{
    unsigned int totalSize = frame->allocated_size(kYPlane) +
        frame->allocated_size(kUPlane) +
        frame->allocated_size(kVPlane);
    const uint8_t *bytes = frame->buffer(kYPlane);
    unsigned char *unChars = const_cast<unsigned char*>(bytes);
    auto finalArray = ref new Platform::Array<unsigned char>(unChars, totalSize);
    auto dataWriter = ref new DataWriter();
    dataWriter->WriteBytes(finalArray);
    auto timeSpan = TimeSpan();
    timeSpan.Duration = frame->timestamp();
    auto sample = MediaStreamSample::CreateFromBuffer(dataWriter->DetachBuffer(), timeSpan);

    return sample;
}

And the corresponding part from .h file is:

private:
    bool inited_;
    bool transcodeStarted_;
    bool isSampleRequestPending_;
    std::list<const I420VideoFrame*> framesToEncode_;
    EncodedImageCallback* encoded_complete_callback_;
    MediaStreamSource ^mediaStreamSource_;
    MediaTranscoder ^mediaTranscoder_;
    InMemoryRandomAccessStream ^encodedStream_;
    MediaEncodingProfile ^h264EncodingProfile_;
    PrepareTranscodeResult ^prepareTranscodeResult_;
    MediaStreamSourceSampleRequest ^sampleRequest_;
    VideoStreamDescriptor ^streamDescriptor_;

    void FeedOneSample(MediaStreamSourceSampleRequest ^sampleRequest);
    MediaStreamSample^ TransformToMediaStreamSample(const I420VideoFrame* frame);

If is useful I can provide logs captured with MFTrace.


Solution

  • So finally, with the help from someone who know better the internals of Microsoft Media Foundation the mistake was found. GetDeferral() should be called when there is no frame available, instead of line args->Request->ReportSampleProgress(1); This call tells to the transcoder that it has to wait a while for a new frame. Also the request must be stored after GetDefferal() is called and used later:

    MediaStreamSourceSampleRequestDeferral ^sampleRequestDefferal_;
    

    Then, when there are frames available the SampleRequest is set with the new frame and sampleRequestDefferal_->Complete() must be called.

    With those modifications the transcoder works continuously.