BUFFER管理是多媒体框架设计实现中的核心任务,在常见的多媒体框架,比如FFMPEG,GST等中,BUFFER管理的代码实现都是复杂且代码量非常大的部分。从某种意义上说,多媒体应用的核心在于BUFFER管理,一个高效易用的BUFFER管理框架不但可以提供友好的开发模型,而且还可以最大限度的挖掘VPU的计算能力,提高多媒体应用的效率。
为什么可以通过零拷贝来优化多媒体框架?一个典型的多媒体解码架构如下图所示:
数据路径可以理解成 下图所示:
如果我们按照数据的性质再进行细分,会发现这样一个现象,在数据传输的前半段,数据的存在的形态都是VBV 数据,也就是原始的编码数据,而在传输的后半段,数据变成了贞数据。
既然是数据的性质相同,那么我们就可以用同一个BUFFER去管理他,不同的组建之间传递数据时,只需要传递这个BUFFER管理池的指针就好了,不用每次组件之间传递数据时,都进行内存拷贝。这种实现方式就是零拷贝。
那么,在GST或FFMPEG以及OMX上有没有实现零拷贝呢?很遗憾,零拷贝是和实现相关的,默认的GST,FFMPEG,以及OMX都没有实现支持零拷贝的底层机制。
OMX组件的非Tunnel模型:
FFMPEG的工作模型:
GST仅仅是一套框架,具体的实现依赖于具体的插件。
一个基于msgget/msgsnd进程异步通信机制的零BUFFER拷贝实现:
根据零BUFFER拷贝的需求,本文设计了一种零Buffer拷贝的实现机制,原理如下:
在生产者和消费者之间,建立起两条管道,一个管道名称为send pipe,用于传递VBV数据,另一个管道叫做return pipe,用于返回使用完毕的空的VBV buffer,这样,这样,生产者发送帧以及消费者消费帧之间,以及消费者还帧和生产者要空帧之间构成了逻辑循环的闭环,可以一直进行下去。的
使用MSTBOX或者其他系统进程间通信机制,可以实现上图中的pipeline,如下图所示的代码实现:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
#include <pthread.h>
//消息的数据结构
struct msg_st
{
long int msg_type;
char text[1024];
};
void *read_msg_server(void *param)
{
int ret=-1;
struct msg_st data;
int msgid=-1, msgid1;
int msgtype = 0;
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid==-1)
{
perror("msgget create fail!");
return NULL;
}
msgid1 = msgget((key_t)4321, 0666 | IPC_CREAT);
if(msgid1==-1)
{
perror("msgget create fail!");
return NULL;
}
while(1)
{
//从队列中获取消息,直到遇到quit为止
ret=msgrcv(msgid,(void*)&data,1024,msgtype,0);
if(ret==-1)
{
perror("msgrcv fail");
break;
}
printf("%s line %d, msg receive:%s\n", __func__, __LINE__, data.text);
data.msg_type=1;
strcpy(data.text,"return");
//向队列发送数据
ret=msgsnd(msgid1,(void*)&data, 1024, 0);
if(ret==-1)
{
perror("msgsnd fail!");
break;
}
}
return NULL;
}
void *write_msg_server(void *param)
{
int ret=-1;
struct msg_st data;
int msgid=-1, msgid1;
int msgtype = 0;
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid==-1)
{
perror("msgget create fail!");
return NULL;
}
msgid1 = msgget((key_t)4321, 0666 | IPC_CREAT);
if(msgid1==-1)
{
perror("msgget create fail!");
return NULL;
}
while(1)
{
data.msg_type=1;
strcpy(data.text,"send");
//向队列发送数据
ret=msgsnd(msgid,(void*)&data, 1024, 0);
if(ret==-1)
{
perror("msgsnd fail!");
break;
}
sleep(1);
ret=msgrcv(msgid1,(void*)&data,1024,msgtype,0);
if(ret==-1)
{
perror("msgrcv fail");
break;
}
printf("%s line %d msg receive:%s\n", __func__, __LINE__, data.text);
}
return NULL;
}
int main()
{
int ret;
int msgid=-1;
long int msgtype = 0; //接收为0,发送为1
pthread_t pthread_read;
pthread_t pthread_write;
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid==-1)
{
perror("msgget create fail!");
return -1;
}
int err = pthread_create(&pthread_read, NULL, read_msg_server, &msgid);
if(err != 0)
{
perror("create pthread failure.");
return -1;
}
err = pthread_create(&pthread_write, NULL, write_msg_server, &msgid);
if(err != 0)
{
perror("create pthread failure.");
return -1;
}
pthread_join(pthread_write, NULL);
pthread_join(pthread_read, NULL);
//删除消息队列
ret=msgctl(msgid,IPC_RMID,0);
if(ret==-1)
{
perror("msgctl delete fail!");
return -1;
}
return 0;
}
运行结果:
对两个管道进行一次封装,封装为一个pipeline对象
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
#include <pthread.h>
//消息的数据结构
struct msg_st
{
long int msg_type;
char text[1024];
};
typedef struct _pipeline_
{
int msgid_pro;
int msgid_con;
}pipeline_t;
void skt_create(pipeline_t *skt)
{
skt->msgid_pro = msgget((key_t)1234, 0666 | IPC_CREAT);
if(skt->msgid_pro ==-1)
{
perror("msgget create fail!");
return;
}
skt->msgid_con = msgget((key_t)4321, 0666 | IPC_CREAT);