aliakseis / FFmpegPlayer

Simple FFmpeg video player
MIT License
151 stars 44 forks source link

grap image and font failed? #26

Open w136111526 opened 3 years ago

w136111526 commented 3 years ago

Hello, I use your player to play the video and display text, before the call of the Present window, like to grab a composite image, why not grab it, how can I modify my code:

RECT srcRect = { 0, 0, m_sourceSize.cx, m_sourceSize.cy };
CRect screenPosition = GetScreenPosition();
CRect target(POINT{}, screenPosition.Size());

ifdef 0

const auto blt = GetVideoProcessBltParams(
    target,
    m_ProcAmpValues,
    m_NFilterValues,
    m_DFilterValues);
const auto sample = GetVideoSample(m_sourceSize, target, m_pMainStream);

hr = m_pDXVAVPD->VideoProcessBlt(m_pD3DRT,
    &blt,
    &sample,
    SUB_STREAM_COUNT + 1,
    NULL);
if (FAILED(hr))
{
    TRACE("VideoProcessBlt failed with error 0x%x.\n", hr);
}

else

m_pD3DD9->Clear(
    0,
    NULL,
    D3DCLEAR_TARGET,
    D3DCOLOR_XRGB(0, 0, 0),
    1.0f,
    0);

hr = m_pD3DD9->StretchRect(
    m_pMainStream,
    &srcRect,
    m_pD3DRT,
    &target,
    D3DTEXF_NONE);
if (FAILED(hr))
{
    TRACE("StretchRect failed with error 0x%x.\n", hr);
}

endif

std::string subtitle = "这个一个测试文字";
if (!subtitle.empty())
{
    RECT srcRect = { 0, 0, m_sourceSize.cx, m_sourceSize.cy };
    CRect screenPosition = GetScreenPosition();
    CRect target(POINT{}, screenPosition.Size());

    //const auto& convertedSubtitle = CA2T(subtitle.c_str(), CP_UTF8);
    HRESULT hr = m_pD3DD9->BeginScene();
    if (SUCCEEDED(hr))
    {
        DrawSubtitleText(m_pD3DD9, target.Width(), target.Height(),
            CA2W(subtitle.c_str(), GetDocument()->isUnicodeSubtitles() ? CP_UTF8 : CP_ACP));

        m_pD3DD9->EndScene();
    }
}
//grab dest image
{
    IDirect3DSurface9 *pBackBuffer;

    HRESULT hr = m_pD3DD9->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
    D3DSURFACE_DESC desc;
    D3DLOCKED_RECT lr;
    pBackBuffer->GetDesc(&desc);
    pBackBuffer->LockRect(&lr, NULL, D3DLOCK_NOSYSLOCK);
    int width = desc.Width;
    int height = desc.Height;
    int nPitch = lr.Pitch;
    unsigned char* data = (unsigned char*)lr.pBits;
    pBackBuffer->UnlockRect();

    if (SUCCEEDED(hr))
        pBackBuffer->Release();
}

hr = m_pD3DD9->Present(&target, &screenPosition, GetSafeHwnd(), NULL);
if (FAILED(hr))
{
    TRACE("Present failed with error 0x%x.\n", hr);
}
aliakseis commented 3 years ago

Hi, The combination of https://gist.github.com/karlgluck/8467971 and https://www.programmersought.com/article/15961657580/ worked for me:

void SaveFrameToJpg(AVFrame *frame, const char* fname)
{
    AVFormatContext *ofmtCtx = NULL;
    AVCodec *codec = NULL;
    AVCodecContext *codecCtx = NULL;
    AVStream *stream = NULL;
    int ret;

    /* allocate the output media context */
    avformat_alloc_output_context2(&ofmtCtx, NULL, NULL, fname);
    if (!ofmtCtx) {
        return;
    }

    codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (!codec) {
        return;
    }

    stream = avformat_new_stream(ofmtCtx, NULL);
    if (!stream) {
        return;
    }

    codecCtx = avcodec_alloc_context3(codec);
    if (!codecCtx) {
        return;
    }

    codecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
    codecCtx->bit_rate = 400000;
    codecCtx->width = frame->width;
    codecCtx->height = frame->height;
    codecCtx->time_base.num = 1;
    codecCtx->time_base.den = 25;

    /* open the codec */
    ret = avcodec_open2(codecCtx, codec, NULL);
    if (ret < 0) {
        return;
    }

    /* open the output file, if needed */
    ret = avio_open(&ofmtCtx->pb, "aa.jpg", AVIO_FLAG_WRITE);
    if (ret < 0) {
        return;
    }

    /* Write the stream header, if any. */
    ret = avformat_write_header(ofmtCtx, NULL);
    if (ret < 0) {
        return;
    }

    AVPacket avpkt = { 0 };
    int got_packet = 0;

    av_init_packet(&avpkt);

    /* encode the image */
    ret = avcodec_send_frame(codecCtx, frame);
    if (ret < 0) {
        return;
    }

    ret = avcodec_receive_packet(codecCtx, &avpkt);
    if (ret < 0) {
        return;
    }

    av_write_frame(ofmtCtx, &avpkt);

    /* Write the trailer, if any. The trailer must be written before you
    * close the CodecContexts open when you wrote the header; otherwise
    * av_write_trailer() may try to use memory that was freed on
    * av_codec_close(). */
    av_write_trailer(ofmtCtx);

    /* Close the output file. */
    avio_closep(&ofmtCtx->pb);

    avcodec_free_context(&codecCtx);

    /* free the stream */
    avformat_free_context(ofmtCtx);
}
    //grab dest image
    {
        // https://gist.github.com/karlgluck/8467971
        // Get the buffer's description and make an offscreen surface in system memory.
        // We need to do this because the backbuffer is in video memory and can't be locked
        // unless the device was created with a special flag (D3DPRESENTFLAG_LOCKABLE_BACKBUFFER).
        // Unfortunately, a video-memory buffer CAN be locked with LockRect.  The effect is
        // that it crashes your app when you try to read or write to it.
        D3DLOCKED_RECT d3dlr;
        D3DSURFACE_DESC desc;
        CComPtr<IDirect3DSurface9> offscreen_surface;
        m_pD3DRT->GetDesc(&desc); // backbuffer
        m_pD3DD9->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format,
            D3DPOOL_SYSTEMMEM, &offscreen_surface, NULL);

        // Copy from video memory to system memory
        m_pD3DD9->GetRenderTargetData(m_pD3DRT, offscreen_surface);

        D3DLOCKED_RECT lr;
        ZeroMemory(&lr, sizeof(D3DLOCKED_RECT));
        hr = offscreen_surface->LockRect(&lr, 0, D3DLOCK_READONLY);
        if (FAILED(hr))
        {
            //printf("Cannot lock rect!");
        }

        if (lr.pBits)
        {
            uint8_t *dst_data[4] = { 0 };
            int dst_linesize[4];
            uint8_t *src_data[4] = { 0 };
            int src_linesize[4];
            struct SwsContext *sws_ctx = NULL;
            AVFrame *frame = NULL;
            int ret;

            sws_ctx = sws_getContext(target.Width(), target.Height(), AV_PIX_FMT_BGRA,
                target.Width(), target.Height(), AV_PIX_FMT_YUV420P,
                SWS_BILINEAR, NULL, NULL, NULL);

            if ((ret = av_image_alloc(dst_data, dst_linesize,
                target.Width(), target.Height(), AV_PIX_FMT_YUV420P, 1)) < 0)
            {
                //fprintf(stderr, "Could not allocate destination image\n");
                //return E_FAIL;
            }

            frame = av_frame_alloc();

            frame->format = AV_PIX_FMT_YUVJ420P;
            frame->width = target.Width();
            frame->height = target.Height();

            src_data[0] = (uint8_t*)lr.pBits;
            src_linesize[0] = lr.Pitch;

            sws_scale(sws_ctx, src_data,
                src_linesize, 0, target.Height(), dst_data, dst_linesize);

            frame->data[0] = dst_data[0];
            frame->data[1] = dst_data[1];
            frame->data[2] = dst_data[2];
            frame->linesize[0] = dst_linesize[0];
            frame->linesize[1] = dst_linesize[1];
            frame->linesize[2] = dst_linesize[2];

            SaveFrameToJpg(frame, "/temp/frame.jpg");

            av_freep(&dst_data[0]);
            av_frame_free(&frame);
            sws_freeContext(sws_ctx);
        }

        hr = offscreen_surface->UnlockRect();
        if (FAILED(hr))
        {
            //printf("Cannot unlock rect!");
        }

    }
w136111526 commented 3 years ago

Thank you very much, but there is one small problem. My input source is an image of 1920 * 1080, but the target client is not this size. Can you grab the original 1080p image。

aliakseis commented 3 years ago

In this case, I would rather generate a bitmap with the frame image from m_pMainStream, then draw text etc. on that bitmap with the help of Gdiplus::Graphics. There is an example of Gdiplus::Graphics usage with bitmaps in DrawSubtitleText...