Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能

在 Android FFmpeg系列——2 播放音频 中,在主线程播放音频会导致ANR,虽然我们可以在 Java 层启动一个线程来播放,由于接下来我们要实现完整播放视频,需要在 C 层达到控制效果,所以我们还是在 C 层启动新线程来播放音频。

这一节,我们来学习 C 层多线程的使用。

pthread

pthread 是 C 语言实现多线程的库,我们要了解这个库的3个相关函数。

  • pthread_create
// 创建线程// typedef long pthread_t;// 参数1:线程 ID,pthread_t* 其实就是 long 类型// 参数2:线程属性,目前置为 NULL,有兴趣可以自己了解一下// 参数3:线程要执行的函数,void* 类似就是 Java 中泛型或者 Object// 参数4:线程要执行函数的参数pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);
  • pthread_join
// 阻塞线程// 参数1:线程 ID// 参数2:变量指针,用来存储被等待线程的返回值// 这个函数是阻塞函数,等待线程函数执行完毕,获取返回值pthread_join(pthread_t __pthread, void** __return_value_ptr);
  • pthread_exit
// 退出线程,一般在线程函数里面调用// 参数1:退出返回值pthread_exit(void* __return_value);

简单使用多线程

直接上码:

/** * 子线程执行函数 * 相当于 Java Runnable 的 run 函数 * @param arg * @return */void* run(void* arg) {    char *name = (char*) arg;    for (int i = 0; i < 10; i++) {        LOGE("Test C Thread : name = %s, i = %d", name, i);        sleep(1);    }    return NULL;}/** * 测试子线程 */extern "C"JNIEXPORT void JNICALLJava_com_johan_player_Player_testCThread(JNIEnv *env, jobject instance) {    // 线程 ID    pthread_t tid1, tid2;    // 创建新线程并启动    pthread_create(&tid1, NULL, run, (void*) "Thread1");    pthread_create(&tid2, NULL, run, (void*) "Thread2");    // 阻塞线程    // pthread_join(tid1, NULL);}

我们启动了两个新线程,run 是线程执行方法,分别打印 0-9 数字,Thread1 和 Thread2 是我传入 run 方法的值,打印结果如下:

10-17 14:11:07.506 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 010-17 14:11:07.506 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 010-17 14:11:08.506 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 110-17 14:11:08.506 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 110-17 14:11:09.507 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 210-17 14:11:09.507 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 210-17 14:11:10.507 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 310-17 14:11:10.507 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 310-17 14:11:11.507 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 410-17 14:11:11.508 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 410-17 14:11:12.508 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 510-17 14:11:12.508 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 510-17 14:11:13.508 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 610-17 14:11:13.509 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 610-17 14:11:14.508 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 710-17 14:11:14.509 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 710-17 14:11:15.509 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 810-17 14:11:15.509 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 810-17 14:11:16.509 15561-15658/com.johan.player E/player: Test C Thread : name = Thread1, i = 910-17 14:11:16.510 15561-15659/com.johan.player E/player: Test C Thread : name = Thread2, i = 9

互斥锁

互斥锁也在 pthread 库中:

  • pthread_mutex_init
// 创建互斥锁// 参数1:线程锁 ID,类似于线程 ID// 参数2:属性,暂时为 NULLpthread_mutex_init(pthread_mutex_t* __mutex, const pthread_mutexattr_t* __attr);
  • pthread_mutex_destroy
// 销毁线程锁// 参数1:线程锁 IDpthread_mutex_destroy(pthread_mutex_t* __mutex);
  • pthread_mutex_lock
// 加锁// 参数1:线程锁 IDpthread_mutex_lock(pthread_mutex_t* __mutex);
  • pthread_mutex_unlock
// 解锁// 参数1:线程锁 IDpthread_mutex_unlock(pthread_mutex_t* __mutex);

当然还有其他函数,可以自己了解一下。

互斥锁一般会与条件变量一起使用,我们接下来看看条件变量。

条件变量

  • pthread_cond_init
// 创建条件变量// 参数1:条件变量 ID,类似于线程 ID// 参数2:属性,暂时为 NULLpthread_cond_init(pthread_cond_t* __cond, const pthread_condattr_t* __attr);
  • pthread_cond_destroy
// 销毁条件变量// 参数1:条件变量 IDpthread_cond_destroy(pthread_cond_t* __cond);
  • pthread_cond_wait
// 线程等待,并释放线程锁// 参数1:条件变量 ID// 参数2:线程锁 IDpthread_cond_wait(pthread_cond_t* __cond, pthread_mutex_t* __mutex);
  • pthread_cond_signal(pthread_cond_t* __cond);
// 通知线程// 参数1:条件变量 IDpthread_cond_signal(pthread_cond_t* __cond);

条件变量也还有其他函数,自己可以了解一下!

到这里,你会发现,其实和 Java 的 ReentrantLock 很相似!!!

生产者与消费者

为了方便理解互斥锁和条件变量的使用,我们使用这种机制来模拟生产者与消费者模式,直接上码。

先定义一个队列:

队列头文件 queue.h

#include #ifndef PLAYER_QUEUE_H#define PLAYER_QUEUE_H// 队列最大值#define QUEUE_MAX_SIZE 50// 节点数据类型typedef uint NodeElement;// 节点typedef struct _Node {    // 数据    NodeElement data;    // 下一个    struct _Node* next;} Node;// 队列typedef struct _Queue {    // 大小    int size;    // 队列头    Node* head;    // 队列尾    Node* tail;} Queue;/** * 初始化队列 * @param queue */void queue_init(Queue* queue);/** * 销毁队列 * @param queue */void queue_destroy(Queue* queue);/** * 判断是否为空 * @param queue * @return */bool queue_is_empty(Queue* queue);/** * 判断是否已满 * @param queue * @return */bool queue_is_full(Queue* queue);/** * 入队 * @param queue * @param element * @param tid * @param cid */void queue_in(Queue* queue, NodeElement element);/** * 出队 (阻塞) * @param queue * @param tid * @param cid * @return */NodeElement queue_out(Queue* queue);#endif //PLAYER_QUEUE_H

队列实现 queue.cpp

#include "queue.h"#include /** * 初始化队列 * @param queue */void queue_init(Queue* queue) {    queue->size = 0;    queue->head = NULL;    queue->tail = NULL;}/** * 销毁队列 * @param queue */void queue_destroy(Queue* queue) {    Node* node = queue->head;    while (node != NULL) {        queue->head = queue->head->next;        free(node);        node = queue->head;    }    queue->head = NULL;    queue->tail = NULL;}/** * 判断是否为空 * @param queue * @return */bool queue_is_empty(Queue* queue) {    return queue->size == 0;}/** * 判断是否已满 * @param queue * @return */bool queue_is_full(Queue* queue) {    return queue->size == QUEUE_MAX_SIZE;}/** * 入队 (阻塞) * @param queue * @param element */void queue_in(Queue* queue, NodeElement element) {    if (queue->size >= QUEUE_MAX_SIZE){        return;    }    Node* newNode = (Node*) malloc(sizeof(Node));    newNode->data = element;    newNode->next = NULL;    if (queue->head == NULL) {        queue->head = newNode;        queue->tail = queue->head;    } else {        queue->tail->next = newNode;        queue->tail = newNode;    }    queue->size += 1;}/** * 出队 (阻塞) * @param queue * @return */NodeElement queue_out(Queue* queue) {    if (queue->size == 0 || queue->head == NULL) {        return NULL;    }    Node* node = queue->head;    NodeElement element = node->data;    queue->head = queue->head->next;    free(node);    queue->size -= 1;    return element;}

一些全局变量:

// 线程锁pthread_mutex_t mutex_id;// 条件变量pthread_cond_t produce_condition_id, consume_condition_id;// 队列Queue queue;// 生产数量#define PRODUCE_COUNT 10// 目前消费数量int consume_number = 0;

生产者函数:

/** * 生产者函数 * @param arg * @return */void* produce(void* arg) {    char* name = (char*) arg;    for (int i = 0; i < PRODUCE_COUNT; i++) {        // 加锁        pthread_mutex_lock(&mutex_id);        // 如果队列满了 等待并释放锁        // 这里为什么要用 while 呢        // 因为 C 的锁机制有 "惊扰" 现象        // 没有达到条件会触发 所以要循环判断        while (queue_is_full(&queue)) {            pthread_cond_wait(&produce_condition_id, &mutex_id);        }        LOGE("%s produce element : %d", name, i);        // 入队        queue_in(&queue, (NodeElement) i);        // 通知消费者可以继续消费        pthread_cond_signal(&consume_condition_id);        // 解锁        pthread_mutex_unlock(&mutex_id);        // 模拟耗时        sleep(1);    }    LOGE("%s produce finish", name);    return NULL;}

消费者函数:

/** * 消费函数 * @param arg * @return */void* consume(void* arg) {    char* name = (char*) arg;    while (1) {        // 加锁        pthread_mutex_lock(&mutex_id);        // 如果队列为空 等待        // 使用 while 的理由同上        while (queue_is_empty(&queue)) {            // 如果消费到生产最大数量 不再等待            if (consume_number == PRODUCE_COUNT) {                break;            }            pthread_cond_wait(&consume_condition_id, &mutex_id);        }        // 如果消费到生产最大数量        // 1.通知还在等待的线程        // 2.解锁        if (consume_number == PRODUCE_COUNT) {            // 通知还在等待消费的线程            pthread_cond_signal(&consume_condition_id);            // 解锁            pthread_mutex_unlock(&mutex_id);            break;        }        // 出队        NodeElement element = queue_out(&queue);        consume_number += 1;        LOGE("%s consume element : %d", name, element);        // 通知生产者可以继续生产        pthread_cond_signal(&produce_condition_id);        // 解锁        pthread_mutex_unlock(&mutex_id);        // 模拟耗时        sleep(1);    }    LOGE("%s consume finish", name);    return  NULL;}

多线程操作:

extern "C"JNIEXPORT void JNICALLJava_com_johan_player_Player_testCThread(JNIEnv *env, jobject instance) {    // 创建队列    queue_init(&queue);    // 线程 ID    pthread_t tid1, tid2, tid3;    // 创建线程锁    pthread_mutex_init(&mutex_id, NULL);    // 创建条件变量    pthread_cond_init(&produce_condition_id, NULL);    pthread_cond_init(&consume_condition_id, NULL);    LOGE("init --- ");    // 创建新线程并启动    // 1个生产线程 2个消费线程    pthread_create(&tid1, NULL, produce, (void*) "producer1");    pthread_create(&tid2, NULL, consume, (void*) "consumer1");    pthread_create(&tid3, NULL, consume, (void*) "consumer2");    // 阻塞线程    pthread_join(tid1, NULL);    pthread_join(tid2, NULL);    pthread_join(tid3, NULL);    // 销毁条件变量    pthread_cond_destroy(&produce_condition_id);    pthread_cond_destroy(&consume_condition_id);    // 销毁线程锁    pthread_mutex_destroy(&mutex_id);    // 销毁队列    queue_destroy(&queue);    LOGE("destroy --- ");}

1个生产线程,2个消费线程,模拟生产者与消费者模式,代码已经注释得比较清楚,相信大家看得懂!

打印结果:

10-18 17:25:16.320 29058-29058/com.johan.player E/player: init --- 10-18 17:25:16.320 29058-29143/com.johan.player E/player: producer1 produce element : 010-18 17:25:16.320 29058-29145/com.johan.player E/player: consumer2 consume element : 010-18 17:25:17.320 29058-29143/com.johan.player E/player: producer1 produce element : 110-18 17:25:17.321 29058-29145/com.johan.player E/player: consumer2 consume element : 110-18 17:25:18.321 29058-29143/com.johan.player E/player: producer1 produce element : 210-18 17:25:18.321 29058-29145/com.johan.player E/player: consumer2 consume element : 210-18 17:25:19.321 29058-29143/com.johan.player E/player: producer1 produce element : 310-18 17:25:19.321 29058-29144/com.johan.player E/player: consumer1 consume element : 310-18 17:25:20.322 29058-29143/com.johan.player E/player: producer1 produce element : 410-18 17:25:20.322 29058-29145/com.johan.player E/player: consumer2 consume element : 410-18 17:25:21.322 29058-29143/com.johan.player E/player: producer1 produce element : 510-18 17:25:21.322 29058-29145/com.johan.player E/player: consumer2 consume element : 510-18 17:25:22.322 29058-29143/com.johan.player E/player: producer1 produce element : 610-18 17:25:22.323 29058-29145/com.johan.player E/player: consumer2 consume element : 610-18 17:25:23.323 29058-29143/com.johan.player E/player: producer1 produce element : 710-18 17:25:23.323 29058-29145/com.johan.player E/player: consumer2 consume element : 710-18 17:25:24.323 29058-29143/com.johan.player E/player: producer1 produce element : 810-18 17:25:24.323 29058-29145/com.johan.player E/player: consumer2 consume element : 810-18 17:25:25.324 29058-29143/com.johan.player E/player: producer1 produce element : 910-18 17:25:25.324 29058-29145/com.johan.player E/player: consumer2 consume element : 910-18 17:25:25.324 29058-29144/com.johan.player E/player: consumer1 consume finish10-18 17:25:26.324 29058-29143/com.johan.player E/player: producer1 produce finish10-18 17:25:26.325 29058-29145/com.johan.player E/player: consumer2 consume finish10-18 17:25:26.326 29058-29058/com.johan.player E/player: destroy --- 

小结

发现自己对 C 很不熟悉,接下来可以加强一下 C 语言的了解!

这里还没有实现子线程播放音频,是因为想放到后面一起来实现!

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. 【Android】java.lang.AssertionError use looper thread, must
  3. Andriod中的Handler机制
  4. Android(安卓)AsyncTask基础知识整理
  5. Android(安卓)Ril库总结
  6. Android解耦库EventBus的使用和源码分析
  7. Android(安卓)SurfaceFlinger服务启动过程源码分析1
  8. Android(安卓)NDK开发学习(四)
  9. android通过线程实现逐行显示信息

随机推荐

  1. Android中的序列化和反序列化
  2. android layout,xml属性
  3. Android数据存取之Databases
  4. Android的BroadcastReciver收不到Broadca
  5. android aar 使用
  6. Android 打造编译时注解解析框架 这只是
  7. android 权限大全总库
  8. Android 源码下载
  9. ANDROID音频系统散记之一:A2dpAudioInterf
  10. 相对布局(RelativeLayout)