音视频流媒体开发【三十五】FFmpeg+QT播放器4-打通UI到

2023-03-28  本文已影响0人  AlanGe

音视频流媒体开发-目录

1 消息队列

1.1 消息对象

typedef struct AVMessage {
    int what;           // 消息类型
    int arg1;           // 参数1
    int arg2;           // 参数2
    void *obj;          // 如果arg1 arg2还不够存储消息则使⽤该参数
    void (*free_l)(void *obj);  // obj的对象是分配的,这⾥要给出函数怎么释放
    struct AVMessage *next; // 下⼀个消息
} AVMessage;

1.2 消息队列对象

typedef struct MessageQueue {   // 消息队列
    AVMessage *first_msg, *last_msg;    // 消息头,消息尾部
    int nb_messages;    // 有多少个消息
    int abort_request;  // 请求终⽌消息队列
    SDL_mutex *mutex;   // 互斥量
    SDL_cond *cond;     // 条件变量
    AVMessage *recycle_msg; // 消息循环使⽤
    int recycle_count;  // 循环的次数,利⽤局部性原理
    int alloc_count;    // 分配的次数
} MessageQueue;

框架

image.png

1.3 消息队列api

// 释放msg的obj资源
void msg_free_res(AVMessage *msg);
// 私有插⼊消息
int msg_queue_put_private(MessageQueue *q, AVMessage *msg);
//插⼊消息
int msg_queue_put(MessageQueue *q, AVMessage *msg);
// 初始化消息
void msg_init_msg(AVMessage *msg);
// 插⼊简单消息,只带消息类型,不带参数
void msg_queue_put_simple1(MessageQueue *q, int what);
// 插⼊简单消息,只带消息类型,只带1个参数
void msg_queue_put_simple2(MessageQueue *q, int what, int arg1);
// 插⼊简单消息,只带消息类型,带2个参数
void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2)
// 释放msg的obj资源
void msg_obj_free_l(void *obj);
//插⼊消息,带消息类型,带2个参数,带obj
void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len);
// 消息队列初始化
void msg_queue_init(MessageQueue *q);
// 消息队列flush,清空所有的消息
void msg_queue_flush(MessageQueue *q);
// 消息销毁
void msg_queue_destroy(MessageQueue *q);
// 消息队列终⽌
void msg_queue_abort(MessageQueue *q);
// 启⽤消息队列
void msg_queue_start(MessageQueue *q);
// 读取消息
/* return < 0 if aborted, 0 if no msg and > 0 if msg. */
int msg_queue_get(MessageQueue *q, AVMessage *msg, int block);
// 消息删除 把队列⾥同⼀消息类型的消息全删除掉
void msg_queue_remove(MessageQueue *q, int what);

2 类名规划和接⼝设计

2.1 类名规划

IjkMediaPlayer FFplayer VideoState

[图片上传失败...(image-ed05f8-1679839403363)]

image.png

int MainWind::message_loop(void *arg)

ui 和播放器核⼼直接的交互有以下⼏种⽅式:

  1. ui直接调⽤IjkMediaPlayer的接⼝
  2. ui发送消息给消息循环线程,然后调⽤IjkMediaPlayer的接⼝
  3. IjkMediaPlayer发消息给消息循环线程,线程调⽤ui的接⼝。

有部分消息是UI和IjkMediaPlayer都有处理,有部分消息只是IjkMediaPlayer要处理。⽐如:

2.2 接⼝函数

重点和难点接⼝解析

难点,以下五个接⼝的作⽤:

播放:
停⽌:

下⾯详细说明这五个接⼝的具体作⽤:

⽐如什么时候该调⽤create创建IjkMediaPlayer,create接⼝本质上做了哪些操作,对于播放器我们⼀直说要划分。

⽬前的接⼝设计
IjkMediaPlayer();
int ijkmp_create(std::function<int(void *)> msg_loop);
int ijkmp_destroy();
// 设置要播放的url
int ijkmp_set_data_source(const char *url);
// 准备播放
int ijkmp_prepare_async();
// 触发播放
int ijkmp_start();
// 停⽌
int ijkmp_stop();
// 暂停
int ijkmp_pause();
// seek到指定位置
int ijkmp_seek_to(long msec);
// 获取播放状态
int ijkmp_get_state();
// 是不是播放中
bool ijkmp_is_playing();
// 当前播放位置
long ijkmp_get_current_position();
// 总⻓度
long ijkmp_get_duration();
// 已经播放的⻓度
long ijkmp_get_playable_duration();
// 设置循环播放
void ijkmp_set_loop(int loop);
// 获取是否循环播放
int ijkmp_get_loop();
// 读取消息
int ijkmp_get_msg(AVMessage *msg, int block);
// 设置⾳量
void ijkmp_set_playback_volume(float volume);

2.3 代码实现步骤

IjkMediaPlayer类
IjkMediaPlayer成员变量
 // 互斥量
    std::mutex mutex_;
    // 真正的播放器
    FFPlayer *ffplayer_ = NULL;
    //函数指针, 指向创建的message_loop,即消息循环函数
//   int (*msg_loop)(void*);
    std::function<int(void *)> msg_loop_ = NULL; // ui处理消息的循环
    //消息机制线程
    std::thread *msg_thread_; // 执⾏msg_loop
//   SDL_Thread _msg_thread;
    //字符串,就是⼀个播放url
    char *data_source_;
    //播放器状态,例如prepared,resumed,error,completed等
    int mp_state_;  // 播放状态
IjkMediaPlayer成员函数
FFPlayer类
FFPlayer成员变量
std::function<int(const Frame *)> video_refresh_callback_ = NULL;
/* ffplay context */
VideoState *is;
const char* wanted_stream_spec[AVMEDIA_TYPE_NB];
FFPlayer成员函数
int ffp_create();
void ffp_reset_internal();
void ffp_destroy_p()
/* playback controll */
int       ffp_prepare_async_l(const char *file_name);
int       ffp_start_l();
int       ffp_stop_l();

消息循环线程

UI MainWind消息循环
int MainWind::message_loop(void *arg)
{
    IjkMediaPlayer *mp = (IjkMediaPlayer *)arg;
    // 线程循环
    qDebug() << "message_loop into";
    while (1) {
        AVMessage msg;
        //取消息队列的消息,如果没有消息就阻塞,直到有消息被发到消息队列。
        int retval = mp->ijkmp_get_msg(&msg, 1);    // 主要处理Java->C的消息

        if (retval < 0)
            break;
        switch (msg.what) {
            case FFP_MSG_FLUSH:
                qDebug() << __FUNCTION__ << " FFP_MSG_FLUSH";
            break;
        case FFP_MSG_PREPARED:
            std::cout << __FUNCTION__ << " FFP_MSG_PREPARED" <<std::endl;
            mp->ijkmp_start();
            break;
        default:
           qDebug()  << __FUNCTION__ << " default " << msg.what ;
          break;
       }

        msg_free_res(&msg);

//       qDebug() << "message_loop sleep, mp:" << mp;
        // 先模拟线程运⾏
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
   }
    qDebug() << "message_loop leave";
}
IjkMediaPlayer消息循环
int IjkMediaPlayer::ijkmp_get_msg(AVMessage *msg, int block)
{
    while (1) {
        int continue_wait_next_msg = 0;

        //取消息,如果没有消息则阻塞。
        int retval = msg_queue_get(&ffplayer_->msg_queue_, msg, block);

        if (retval <= 0)        // -1 abort, 0 没有消息
            return retval;
        switch (msg->what) {
            case FFP_MSG_PREPARED:
            std::cout << __FUNCTION__ << " FFP_MSG_PREPARED" <<std::endl;
            break;
            case FFP_REQ_START:
            std::cout << __FUNCTION__ << " FFP_REQ_START" << std::endl;
            continue_wait_next_msg = 1;
            break;
          default:
            std::cout << __FUNCTION__ << " default " << msg->what <<std::endl;
            break;
       }

        if (continue_wait_next_msg) {
            msg_free_res(msg);
            continue;
       }

        return retval;
   }

     return -1;
}

3 补充知识

参考1:Android中MediaPlayer的setDataSource⽅法的使⽤
https://blog.csdn.net/yanlinembed/article/details/51887642

ijkmp_set_data_source的设计来源于Android的MediaPlayer,可以通过重载接⼝提供不同的资源类型。

MediaPlayer的setDataSource()⽅法主要有四种:

Sets the data source as a content Uri.
@param context the Context to use when resolving the Uri
@param uri the Content URI of the data you want to play public void setDataSource(Context context, Uri uri)

Sets the data source (file-path or http/rtsp URL) to use.
@param path the path of the file, or the http/rtsp URL of the stream you want to play public void setDataSource(String path)

Sets the data source (FileDescriptor) to use. It is the caller’s responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
@param fd the FileDescriptor for the file you want to play public void setDataSource(FileDescriptor fd)
Sets the data source (FileDescriptor) to use. The FileDescriptor must be seekable (N.B. a LocalSocket is not seekable). It is the caller’s responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
@param fd the FileDescriptor for the file you want to play
@param offset the offset into the file where the data to be played starts, in bytes
@param length the length in bytes of the data to be played public void setDataSource(FileDescriptor fd, long offset, long length)

1. 播放应⽤的资源⽂件

法1. 直接调⽤create函数实例化⼀个MediaPlayer对象,播放位于res/raw/test.mp3⽂件
MediaPlayer  mMediaPlayer = MediaPlayer.create(this, R.raw.test);

法2. test.mp3放在res/raw/⽬录下,使⽤setDataSource(Context context, Uri uri)
mp = new MediaPlayer();
Uri setDataSourceuri = Uri.parse("android.[resource://com.android.sim/"+R.raw.test);](resource://com.android.sim/)
mp.setDataSource(this, uri);

说明:此种⽅法是通过res转换成uri然后调⽤setDataSource()⽅法,需要注意格式
Uri.parse("android.resource://[应⽤程序包名Application packagename]/"+R.raw.播放⽂件名);

例⼦中的包名为com.android.sim,播放⽂件名为:test;特别注意包名后的"/"。

法3\. test.mp3⽂件放在assets⽬录下,使⽤setDataSource(FileDescriptor fd, longoffset, long length)
AssetManager assetMg = this.getApplicationContext().getAssets();
AssetFileDescriptor fileDescriptor = assetMg.openFd("test.mp3");  
mp.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());
  1. 播放存储设备的资源⽂件
MediaPlayer mediaPlayer = new MediaPlayer();  
mediaPlayer.setDataSource("/mnt/sdcard/test.mp3");
  1. 播放远程的资源⽂件
Uri uri = Uri.parse("http://**");  
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(Context, uri);
上一篇 下一篇

猜你喜欢

热点阅读