libvlc —— 播放器示例程序[C++代碼實現攫取 RGB圖像 和 PCM音頻 數據功能]


在我以前的實際項目中,曾利用 libvlc 去解碼音視頻媒體數據(如 RTSP、本地文件 等),通過其提供的回調函數接口,攫取 RGB圖像 進行圖像分析,如 人臉識別、運動檢測 等一類的產品應用。除此之外,只要提供適當的 MRL,配合選項參數,VLC 還可以進行屏幕錄制、攝像頭圖像采集、麥克風音頻采集 等功能。

我在網上參看過很多人提供的示例源碼,實現流程都很初潛,只適合當作學習的 Demo 來看,與實際的項目應用還有很多問題要解決。為此,在這里公開我封裝 libvlc 的 C++ 類,方便TA人吧!

一、獲取源碼

1. 下載地址

Github: https://github.com/Gaaagaa/MediaGrabber

2. 編譯提醒

這個測試程序,是使用 QtCreator 寫的 Qt 界面程序,調用我封裝好的 vlc_mgrabber_t 類實現了一個簡單的播放器。MFC的我也寫過相應的測試程序,這里就不重復提供代碼了。

因 libvlc 庫相關的 dll 文件太多、太大,上傳不易,所以在完成編譯后,需要另外將 libvlc 的 dll 拷貝至 exe 程序目錄中才能運行,我使用的 libvlc 版本是 vlc-3.0.7.1 版,下面是下載地址:

拿到下載后的壓縮文件,解壓出來,將 libvlc.dlllibvlccore.dllplugins 整個目錄 這幾個拷貝到 exe 程序目錄即可。另外,有一點需要特別提醒,壓縮包中的 sdk 目錄 是我們開發時需要用到的頭文件和鏈接文件。

3. 測試程序截圖

image

二、如何使用

使用 vlc_mgrabber_t 很簡單,我們只需實現主要的 視頻回調、音頻回調、事件回調 三個接口,然后調用其基本操作接口進行 打開、關閉 操作,就可完成基本的工作流程。下面,我逐個說明這些接口的調用方式。

1. 基本操作流程

主要操作接口的聲明如下:

class vlc_mgrabber_t
{
    ......

public:
    /**********************************************************/
    /**
     * @brief Startup the libvlc.
     */
    static x_int32_t startup(x_int32_t xit_argc, const x_char_t * const * xct_argv;
    
    /**********************************************************/
    /**
     * @brief Cleanup the libvlc.
     */
    static x_void_t cleanup(void);

public:
    /**********************************************************/
    /**
     * @brief 設置回調接口。
     *
     * @param [in ] xfunc_vptr   : 視頻圖像的回調函數接口。
     * @param [in ] xfunc_aptr   : 音頻數據的回調函數接口。
     * @param [in ] xfunc_eptr   : 操作事件的回調函數接口。
     * @param [in ] xpvt_context : 回調的用戶上下文描述信息。
     */
    inline x_void_t set_callback(xfunc_video_cbk_t xfunc_vptr,
                                 xfunc_audio_cbk_t xfunc_aptr,
                                 xfunc_event_cbk_t xfunc_eptr,
                                 x_pvoid_t xpvt_context)
    {
        m_xfunc_video_cbk = xfunc_vptr;
        m_xfunc_audio_cbk = xfunc_aptr;
        m_xfunc_event_cbk = xfunc_eptr;
        m_xpvt_xfunc_ctxt = xpvt_context;
    }

    /**********************************************************/
    /**
     * @brief 打開工作流程(操作前請先調用 set_callback() 設置好回調參數)。
     *
     * @param [in ] xht_instance    : 關聯的 libvlc 實例句柄(若為 X_NULL,則取全局的實例句柄)。
     * @param [in ] xszt_media_file : 音視頻文件路徑名。
     * @param [in ] xszt_options    : 附加的參數選項(使用 " :" (空格+':')進行分隔的字符串集,為 X_NULL 時,則忽略)。
     * @param [in ] xut_video_nbits : 視頻回調操作時的 RGB 圖像位數(24 或 32)。
     *
     * @return x_int32_t
     *         - 成功,返回 0;
     *         - 失敗,返回 錯誤碼。
     */
    x_int32_t open(x_handle_t xht_instance,
                   x_cstring_t xszt_media_file,
                   x_cstring_t xszt_options,
                   x_uint32_t xut_video_nbits);

    /**********************************************************/
    /**
     * @brief 關閉工作流程。
     */
    x_void_t close(void);

    ......

public:

    ......

    /**
     * Set movie position as percentage between 0.0 and 1.0. 
     * This has no effect if playback is not enabled.
     * This might not work depending on the underlying input format and protocol.
     *
     * \param xft_pos the position
     */
    x_void_t set_position(x_float_t xft_pos);

    ......

    /**
     * Set mute status.
     *
     * \param xbt_status If status is X_TRUE then mute, otherwise unmute
     * \warning This function does not always work. If there are no active audio
     * playback stream, the mute status might not be available. If digital
     * pass-through (S/PDIF, HDMI...) is in use, muting may be unapplicable. Also
     * some audio output plugins do not support muting at all.
     * \note To force silent playback, disable all audio tracks. This is more
     * efficient and reliable than mute.
     */
    x_void_t audio_set_mute(x_bool_t xbt_status);

    ......

    /**
     * Set current software audio volume.
     *
     * \param xit_volume the volume in percents (0 = mute, 100 = 0dB)
     * \return 0 if the volume was set, -1 if it was out of range
     */
    x_int32_t audio_set_volume(x_int32_t xit_volume);

    ......
};

使用的大致流程,如下描述:

  • 1 在程序啟動時,調用 vlc_mgrabber_t::startup(0, NULL) 初始化 libvlc 庫。
  • 2 程序執行過程中,開啟一個 vlc_mgrabber 對象 object 的工作流程,需要先后調用如下接口:
    • 2.1 先使用 object.set_callback(...) 設置回調接口;
    • 2.2 然后用 object.open(...) 操作接口打開工作流程;
    • 2.3 期間,用 set_position(...) 設置當前播放進度,audio_set_mute(...) 設置靜音狀態,audio_set_volume(...) 設置播放音量,或者還可以進行其他的操作(詳細請查看源碼);
    • 2.4 另外,libvlc 內部的工作線程,會通過設置的回調函數接口,回調數據(RGB圖像 或 PCM 音頻數據,以及 通知事件);
    • 2.5 最后用 object.close(...) 操作接口關閉工作流程。
  • 3 程序在退出前,執行 vlc_mgrabber_t::cleanup() 卸載 libvlc 庫。

接下來,繼續介紹 數據 和 事件 的回調操作接口要如何實現,以及需要注意的問題。

2. 實現視頻回調接口

若需要進行視頻幀的圖像攫取操作,object.set_callback(...) 時,就必須設置好視頻的回調函數接口。下面先看視頻回調接口的相關數據聲明,如下所示:

class vlc_mgrabber_t
{
    ......

public:
    /**
     * @enum  x_video_callback_prototype_t
     * @brief Callback prototype for video.
     */
    typedef enum __video_callback_prototype__
    {
        VIDEO_CALLBACK_FORMAT  = 1,  ///< Callback prototype to notice the frame's size format.
        VIDEO_CALLBACK_LOCK    = 2,  ///< Callback prototype to allocate and lock a frame buffer.
        VIDEO_CALLBACK_UNLOCK  = 3,  ///< Callback prototype to unlock a frame buffer.
        VIDEO_CALLBACK_DISPLAY = 4,  ///< Callback prototype to display a frame.
    } x_video_callback_prototype_t;

    /**
     * @struct x_video_callback_data_t
     * @brief  Callbakc data for video.
     */
    typedef struct __video_callback_data__
    {
        x_handle_t   xht_handle;   ///< the user-defined handle.
        x_byte_t   * xbt_bits_ptr; ///< the buffer for video frame output.
        x_int32_t    xit_pitch;    ///< the buffer line stride.
        x_int32_t    xit_width;    ///< the real frame's width.
        x_int32_t    xit_height;   ///< the real frame's height.
    } x_video_callback_data_t;

    /**
     * @brief Callback function type for video.
     *
     * @param [in    ] xit_ptype : callback prototype, @see x_video_callback_prototype_t.
     * @param [in,out] xvct_dptr : interactive data for callback operations.
     * @param [in    ] xpvt_ctxt : the user context description.
     */
    typedef x_void_t (* xfunc_video_cbk_t)(x_int32_t xit_ptype,
                                           x_video_callback_data_t * xvct_dptr,
                                           x_pvoid_t xpvt_ctxt);

    ......
};

視頻的回調,有四個類型,描述如下:

  • VIDEO_CALLBACK_FORMAT: 幀信息的通知操作,借此可知道后續回調的圖像基本描述信息(x_video_callback_data_t 的字段中有描述);這個只在打開工作流程后,進行幾次回調通知,之后就再沒有了。在我的測試中,不大理解的是,libvlc 竟然回調了 3 次該類型。
  • VIDEO_CALLBACK_LOCK: 幀圖像的輸出緩存申請操作,通過回寫設置 x_video_callback_data_t 的 xbt_bits_ptr 字段實現;注意,申請的緩存必須足夠大,且不能為 NULL。
  • VIDEO_CALLBACK_UNLOCK:幀圖像完成解碼后的通知操作。
  • VIDEO_CALLBACK_DISPLAY:幀圖像的顯示通知操作;其實可以忽略該回調操作,在 UNLOCK 回調時一並完成顯示操作即可。

注意:視頻的回調過程中,每一幀都會經歷:LOCK、UNLOCK、DISPLAY 三個步驟,所以期間這三個類型的回調是多次發生的。同時,我們要學會利用好 x_video_callback_data_t 的 xht_handle 字段進行操作標識。

下面請看我的測試程序中,是這樣實現回調流程的:

/**********************************************************/
/**
 * @brief Callback function for video.
 *
 * @param [in    ] xit_ptype : callback prototype.
 * @param [in,out] xvct_dptr : interactive data for callback operations.
 */
x_void_t Widget::real_video_cbk(x_int32_t xit_ptype, x_vcbk_data_t * xvct_dptr)
{
    switch (xit_ptype)
    {
        // FORMAT 回調時,初始化圖像渲染的顯示控件,
        // 要注意的是,以 xvct_dptr->xit_height + 16 保證開辟的圖像輸出緩存足夠大
    case vlc_mgrabber_t::VIDEO_CALLBACK_FORMAT:
        if (!ui->widget_render->isStart())
        {
#if 0
            ui->widget_render->startRender(xvct_dptr->xit_width,
                                           xvct_dptr->xit_height + 16,
                                           32);
#else
            emit real_start_render(xvct_dptr->xit_width,
                                   xvct_dptr->xit_height + 16,
                                   32);
#endif
        }
        else if ((xvct_dptr->xit_width  > ui->widget_render->cxImage()) ||
                 (xvct_dptr->xit_height > ui->widget_render->cyImage()))
        {
#if 0
            ui->widget_render->stopRender();
            ui->widget_render->startRender(xvct_dptr->xit_width,
                                           xvct_dptr->xit_height + 16,
                                           32);
#else
            emit real_stop_render();
            emit real_start_render(xvct_dptr->xit_width,
                                   xvct_dptr->xit_height + 16,
                                   32);
#endif
        }
        break;

        // LOCK 回調時,申請圖像輸出緩存,回寫 x_video_callback_data_t 的 xbt_bits_ptr 字段,
        // 同時設置 xht_handle 字段,是為了在 UNLOCK 回調時,知道原來 bits 緩存關聯的對象。
    case vlc_mgrabber_t::VIDEO_CALLBACK_LOCK:
        if (ui->widget_render->isStart())
        {
            QImage * ximage_ptr = ui->widget_render->pull();
            xvct_dptr->xht_handle   = (x_handle_t)ximage_ptr;
            xvct_dptr->xbt_bits_ptr = ximage_ptr->bits();
        }
        break;

        // UNLOCK 回調后,將完成解碼后得到的圖像幀放入渲染控件,通知其刷新顯示
    case vlc_mgrabber_t::VIDEO_CALLBACK_UNLOCK:
        if (ui->widget_render->isStart())
        {
            QImage * ximage_ptr = (QImage *)xvct_dptr->xht_handle;
            ui->widget_render->push(ximage_ptr, xvct_dptr->xit_width, xvct_dptr->xit_height);
        }
        break;

    case vlc_mgrabber_t::VIDEO_CALLBACK_DISPLAY:
        {

        }
        break;

    default:
        break;
    }
}

3. 實現音頻回調接口

要進行音頻數據的攫取操作,object.set_callback(...) 時,設置了音頻的回調函數接口即可。但與此同時,播放過程就會出現無聲狀態,畢竟此時的所有音頻數據都已經流向了用戶的回調接口,音頻輸出設備未能接收到數據。音頻回調接口的相關數據聲明,如下所示:

class vlc_mgrabber_t
{
    ......

public:

    ......

    /**
     * @enum  x_audio_callback_prototype_t
     * @brief Callback prototype for audio.
     */
    typedef enum __audio_callback_prototype__
    {
        AUDIO_CALLBACK_FORMAT        = 1,   ///< Callback prototype for audio format.
        AUDIO_CALLBACK_PLAYBACK      = 2,   ///< Callback prototype for audio playback.
        AUDIO_CALLBACK_PAUSE         = 3,   ///< Callback prototype for audio pause.
        AUDIO_CALLBACK_RESUMPTION    = 4,   ///< Callback prototype for audio resumption (i.e. restart from pause).
        AUDIO_CALLBACK_BUFFER_FLUSH  = 5,   ///< Callback prototype for audio buffer flush.
        AUDIO_CALLBACK_BUFFER_DRAIN  = 6,   ///< Callback prototype for audio buffer drain.
        AUDIO_CALLBACK_VOLUME_CHANGE = 7,   ///< Callback prototype for audio volume change.
    } x_audio_callback_prototype_t;

    /**
     * @brief Callback function type for audio.
     * xit_ptype:
     *      1, Callback prototype for audio format.
     *         xpvt_dptr == X_NULL.
     *      2, Callback prototype for audio playback.
     *         xpvt_dptr == pointer to the first audio sample to play back.
     *      3, Callback prototype for audio pause.
     *         xpvt_dptr == X_NULL.
     *      4, Callback prototype for audio resumption (i.e. restart from pause).
     *         xpvt_dptr == X_NULL.
     *      5, Callback prototype for audio buffer flush.
     *         xpvt_dptr == X_NULL.
     *      6, Callback prototype for audio buffer drain.
     *         xpvt_dptr == X_NULL.
     *      7, Callback prototype for audio volume change.
     *         xpvt_dptr == { x_float_t  : volume software volume (1. = nominal, 0. = mute),
     *                        x_uint32_t : muted flag. }.
     *         xut_size  == sizeof(x_float_t) + sizeof(x_uint32_t) .
     *
     * @param [in ] xit_ptype : the callback type, @see x_audio_callback_prototype_t.
     * @param [in ] xpvt_dptr : the callback data.
     * @param [in ] xut_size  : the callback data's size.
     * @param [in ] xit_pts   : time stamp.
     * @param [in ] xpvt_ctxt : the user context description.
     */
    typedef x_void_t (* xfunc_audio_cbk_t)(x_int32_t xit_ptype,
                                           x_pvoid_t xpvt_dptr,
                                           x_uint32_t xut_size,
                                           x_int64_t xit_pts,
                                           x_pvoid_t xpvt_ctxt);

    ......
};

音頻回調,主要關心 AUDIO_CALLBACK_FORMAT 和 AUDIO_CALLBACK_PLAYBACK 這兩個回調即可:

  • AUDIO_CALLBACK_FORMAT:格式信息的回調通知,可確定后續音頻(PCM)數據的 通道數量、采樣率、每采樣位數 這些信息。
  • AUDIO_CALLBACK_PLAYBACK:音頻數據輸出的回調通知,回調的數據即為 每個采樣點 PCM音頻數據。

在我的測試程序中,是如下代碼實現的,為此,還寫了個 WAV 格式的文件輸出工具類(wave_file.h 里面的 x_wave_file_writer_t 類)。

/**********************************************************/
/**
 * @brief Callback function type for audio.
 * xit_ptype:
 *      1, Callback prototype for audio format.
 *         xpvt_dptr == X_NULL.
 *      2, Callback prototype for audio playback.
 *         xpvt_dptr == pointer to the first audio sample to play back.
 *      3, Callback prototype for audio pause.
 *         xpvt_dptr == X_NULL.
 *      4, Callback prototype for audio resumption (i.e. restart from pause).
 *         xpvt_dptr == X_NULL.
 *      5, Callback prototype for audio buffer flush.
 *         xpvt_dptr == X_NULL.
 *      6, Callback prototype for audio buffer drain.
 *         xpvt_dptr == X_NULL.
 *      7, Callback prototype for audio volume change.
 *         xpvt_dptr == { x_float_t : volume software volume (1. = nominal, 0. = mute),
 *                        x_int32_t : muted flag. }.
 *
 * @param [in ] xit_ptype : the callback type, @see x_audio_callback_prototype_t.
 * @param [in ] xpvt_dptr : the callback data.
 * @param [in ] xut_size  : the callback data's size.
 * @param [in ] xit_pts   : time stamp.
 */
x_void_t Widget::real_audio_cbk(x_int32_t xit_ptype,
                                x_pvoid_t xpvt_dptr,
                                x_uint32_t xut_size,
                                x_int64_t xit_pts)
{
    switch (xit_ptype)
    {
        // FORMAT 回調,是在 open() 操作后進行的格式通知,
        // 借此,可以知道后續回調的 PCM 音頻數據的 通道數量、采樣率、每個采樣位數 這些信息
    case vlc_mgrabber_t::AUDIO_CALLBACK_FORMAT:
        if (!m_wfile_writer.is_open())
        {
            QByteArray  text_file = ui->lineEdit_audioFile->text().toUtf8();
            std::string xstr_file = text_file.data();

            m_wfile_writer.open(xstr_file.c_str(),
                                (x_uint16_t)m_xvlc_mgrabber.get_audio_channels(),
                                m_xvlc_mgrabber.get_audio_rate(),
                                (x_uint16_t)m_xvlc_mgrabber.get_audio_bits_per_sample());
        }
        break;

        // PLAYBACK 回調,這是回調 PCM 音頻數據的
    case vlc_mgrabber_t::AUDIO_CALLBACK_PLAYBACK:
        if (m_wfile_writer.is_open())
        {
            m_wfile_writer.write((x_uchar_t *)xpvt_dptr, xut_size);
        }
        break;

    default:
        break;
    }
}

4. 實現事件回調接口

最后,是工作流程中的事件回調接口,先看相關的數據聲明:

class vlc_mgrabber_t
{
    ......

public:

    ......

    /**
     * @enum  x_event_callback_prototype_t
     * @brief Callback prototype for event.
     */
    typedef enum __event_callback_prototype__
    {
        EVENT_CALLBACK_END_REACHED      = 265,   ///< media player end reached.
        EVENT_CALLBACK_TIME_CHANGED     = 267,   ///< media player time changed.
        EVENT_CALLBACK_POSITION_CHANGED = 268,   ///< media player position changed.
        EVENT_CALLBACK_LENGTH_CHANGED   = 273,   ///< media player length changed.
    } x_event_callback_prototype_t;

    /**
     * @brief Callback function type for event.
     *
     * @param [in ] xit_event   : the callback event code, @see x_event_callback_prototype_t.
     * @param [in ] xlpt_param1 : reserved parameter.
     * @param [in ] xlpt_param2 : reserved parameter.
     * @param [in ] xpvt_ctxt   : the user context description.
     */
    typedef x_void_t (* xfunc_event_cbk_t)(x_int32_t xit_event,
                                           x_lptr_t xlpt_param1,
                                           x_lptr_t xlpt_param2,
                                           x_pvoid_t xpvt_ctxt);

    ......
};

vlc_mgrabber_t 在 open(...) 操作中,只注冊了 4 個事件(x_event_callback_prototype_t 的四個),若實際應用中,仍不夠,就請參考 libvlc 中 libvlc_event_e 所枚舉的事件,照貓畫虎的在 vlc_mgrabber_t.open(...) 增加代碼了。

通常,我們只需要關心 EVENT_CALLBACK_END_REACHED (播放結束)事件就可以了。需要特別注意的是,事件回調接口中的代碼,是由 libvlc 內部開啟的線程執行的,所以在我們在收到 EVENT_CALLBACK_END_REACHED 事件通知后,必須以 異步通知的方式 執行 object.close() 關閉操作,例如我的示例程序中是這樣實現的:

......

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ......

    connect(this, SIGNAL(real_end_reached()),
            this, SLOT(on_pushButton_stop_clicked()),
            Qt::QueuedConnection);

    ......
}

......

/**********************************************************/
/**
 * @brief Callback function for event.
 *
 * @param [in ] xit_event   : the callback event code.
 * @param [in ] xlpt_param1 : param1.
 * @param [in ] xlpt_param2 : param2.
 */
x_void_t Widget::real_event_cbk(x_int32_t xit_event,
                                x_lptr_t xlpt_param1,
                                x_lptr_t xlpt_param2)
{
    switch (xit_event)
    {
    case vlc_mgrabber_t::EVENT_CALLBACK_END_REACHED:
        {
            // 異步方式,通知播放結束事件
            emit real_end_reached();
        }
        break;

    default:
        break;
    }
}

void Widget::on_pushButton_stop_clicked()
{
    m_xvlc_mgrabber.close();
    ......
}

三、幾個關鍵的 libvlc API

集中在 vlc_mgrabber_t.open(...) 的實現流程中,使用到的幾個關鍵 API:


LIBVLC_API libvlc_media_t *libvlc_media_new_location(
                                   libvlc_instance_t *p_instance,
                                   const char * psz_mrl );

LIBVLC_API
void libvlc_video_set_callbacks( libvlc_media_player_t *mp,
                                 libvlc_video_lock_cb lock,
                                 libvlc_video_unlock_cb unlock,
                                 libvlc_video_display_cb display,
                                 void *opaque );

LIBVLC_API
void libvlc_video_set_format_callbacks( libvlc_media_player_t *mp,
                                        libvlc_video_format_cb setup,
                                        libvlc_video_cleanup_cb cleanup );

LIBVLC_API
void libvlc_audio_set_callbacks( libvlc_media_player_t *mp,
                                 libvlc_audio_play_cb play,
                                 libvlc_audio_pause_cb pause,
                                 libvlc_audio_resume_cb resume,
                                 libvlc_audio_flush_cb flush,
                                 libvlc_audio_drain_cb drain,
                                 void *opaque );

LIBVLC_API
void libvlc_audio_set_volume_callback( libvlc_media_player_t *mp,
                                       libvlc_audio_set_volume_cb set_volume );

LIBVLC_API
void libvlc_audio_set_format_callbacks( libvlc_media_player_t *mp,
                                        libvlc_audio_setup_cb setup,
                                        libvlc_audio_cleanup_cb cleanup );

LIBVLC_API libvlc_event_manager_t * libvlc_media_player_event_manager ( libvlc_media_player_t *p_mi );

LIBVLC_API int libvlc_event_attach( libvlc_event_manager_t *p_event_manager,
                                        libvlc_event_type_t i_event_type,
                                        libvlc_callback_t f_callback,
                                        void *user_data );

LIBVLC_API int libvlc_media_player_play ( libvlc_media_player_t *p_mi );

四、總結

libvlc 的回調操作,對於視頻圖像來說,並不僅限於 RGB 格式,YUV 格式也是可以的,但這方面我並未去嘗試,畢竟接觸到的應用場景使用 RGB 格式的圖像使用更廣泛。至少,對於很多進行圖像算法分析的工作,使用 RGB 格式的更多一些。若是需要進行其他格式的圖像回調,就需要另行調整代碼了。

對於音頻的回調格式,我也曾嘗試過按照指定格式進行數據回調(比如,回調 8 位采樣點、單通道的 PCM),並未成功。就我目前的工作而言,攫取音頻數據的場景不多,這問題也就沒過多糾結。

而字幕流的回調,在 vlc_mgrabber_t 更是沒有去實現,libvlc 中是有這些接口的,感興趣的朋友可以自行研究。

最后,要是上面的代碼在使用過程中,有任何問題或建議,歡迎請在評論區留言聯系我 🙂


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM