ffmpeg播放器4

86 阅读4分钟

1背景

hplayer学习4 上一篇追踪完了工厂方法创建HFFPlayer,这一篇继续后续的代码

2 步骤

2.1 start后续

void HVideoWidget::start() {
    if (media.type == MEDIA_TYPE_NONE) {
        QMessageBox::information(this, tr("Info"), tr("Please first set media source, then start."));
        updateUI();
        return;
    }

    if (!pImpl_player) {
        pImpl_player = HVideoPlayerFactory::create(media.type);
        pImpl_player->set_media(media);
        pImpl_player->set_event_callback(hplayer_event_callback, this);
        title = media.src.c_str();
        int ret = pImpl_player->start();
        if (ret != 0) {
            onOpenFailed();
        }
        else {
            onOpenSucceed();
        }
        updateUI();
    }
    else {
        if (status == PAUSE) {
            resume();
        }
    }
}

工厂方法创建HFFPlayer后,传入了从之前选择的媒体文件信息(src,type等等)

2.2 set_media

pImpl_player->set_media(media);

追踪HFFPlayer(src/video/HFFPlayer.h)没看到有对应的方法,继续从基类(HVideoPlayer)找

class HFFPlayer : public HVideoPlayer, public HThread 

基类的头文件中可以看到如下定义

class HVideoPlayer
{
public:
    void set_media(HMedia& media) {
        this->media = media;
    }
public:
    HMedia      media;

成员函数在头文件实现了,也没啥问题,最多头文件冗余

继续之前start后续

2.3 set_event_callback

之前的pImpl_player,上一篇里有从头文件(src/ui/HVideoWidget.h)找到如下声明

class HVideoWidget : public QFrame
{
private:
    HVideoPlayer*   pImpl_player;

在HVideoPlayer的头文件(src/video/HVideoPlayer.h)中可以找到如下代码

class HVideoPlayer
{
public:
    void set_event_callback(hplayer_event_cb cb, void* userdata) {
        event_cb = cb;
        event_cb_userdata = userdata;
    }

    void event_callback(hplayer_event_e e) {
        if (event_cb) {
            event_cb(e, event_cb_userdata);
        }
    }

set_event_callback使用了两个成员变量,查看头文件可以找到如下代码

class HVideoPlayer
{
protected:
    hplayer_event_cb    event_cb;
    void*               event_cb_userdata;

先看hplayer_event_cb,这个从当前头文件可以看到有如下申明

enum hplayer_event_e {
    HPLAYER_OPEN_FAILED,
    HPLAYER_OPENED,
    HPLAYER_EOF,
    HPLAYER_CLOSED,
    HPLAYER_ERROR,
};
typedef int (*hplayer_event_cb)(hplayer_event_e e, void* userdata);

这里用了函数指针

如果不用typedef会导致下面的问题
1. 代码冗余,可读性差
每次声明函数指针变量或参数时,都需要重复完整的函数指针语法,导致代码冗长且难以阅读。
// 使用 typedef 的简洁写法
typedef int (*hplayer_event_cb)(hplayer_event_e e, void* userdata);
hplayer_event_cb callback;  // 直接使用类型名

// 不使用 typedef 的冗余写法
int (*callback)(hplayer_event_e e, void* userdata);  // 每次声明都要写完整语法
2. 函数参数声明复杂
当函数指针作为参数传递时,不使用  typedef  会导致函数声明极其臃肿,降低可读性。
// 使用 typedef 的清晰写法
void register_callback(hplayer_event_cb callback, void* userdata);

// 不使用 typedef 的复杂写法
void register_callback(int (*callback)(hplayer_event_e e, void* userdata), void* userdata);
3. 类型修改困难
4. 容易引发语法错误
5. 无法直接复用类型
6. 降低代码抽象性
 typedef  的作用是为复杂类型赋予一个有意义的名称(如  hplayer_event_cb ),使代码更符合“自文档化”原则。不使用  typedef  时,代码更像是“魔法字符串”,难以理解其设计意图。

在需要 多次使用同一函数指针类型 或 提升代码可维护性 时,务必使用  typedef 。这是 C/C++ 中处理复杂类型的标准实践!

另一个event_cb_userdata就是任意数据了  void* :通用指针类型,可指向任意数据类型,提供灵活性。

上面的是pImpl_player的申明和定义,再看传入的参数

下面的代码是在(src/ui/HVideoWidget.cpp)中的,传入的this就是指HVideoWidget了

pImpl_player->set_event_callback(hplayer_event_callback, this);

传入的第一个在src/ui/HVideoWidget.h没找到,但是在HVideoWidget.cpp里找到了如下

static int hplayer_event_callback(hplayer_event_e e, void* userdata) {
    HVideoWidget* wdg = (HVideoWidget*)userdata;
    int custom_event_type = QCustomEvent::User;
    switch (e) {
    case HPLAYER_OPENED:
        custom_event_type = QCustomEvent::OpenMediaSucceed;
        break;
    case HPLAYER_OPEN_FAILED:
        custom_event_type = QCustomEvent::OpenMediaFailed;
        break;
    case HPLAYER_EOF:
        custom_event_type = QCustomEvent::PlayerEOF;
        break;
    case HPLAYER_ERROR:
        custom_event_type = QCustomEvent::PlayerError;
        break;
    default:
        return 0;
    }
    hlogi("postEvent %d", custom_event_type);
    QApplication::postEvent(wdg, new QEvent((QEvent::Type)custom_event_type));
    return 0;
}

 static  限制了函数的作用域为当前文件。如果是纯c++调用,可以用匿名名称空间。

 static  关键字使符号(函数/变量)具有 内部链接性,每个编译单元( .c / .cpp )会生成自己的副本,链接时不会冲突。

 static  在头文件中不会导致编译/链接错误,但会导致代码冗余和逻辑问题。

匿名命名空间:仅用于  .cpp  文件,定义当前文件私有的内容。 头文件:声明需要跨文件共享的接口(如类、普通命名空间中的函数)。 替代方案:需共享的工具函数应通过普通命名空间或静态成员函数实现。

2.4 待续