no message
commit
833d7dc068
|
@ -0,0 +1,82 @@
|
|||
#include "toast.h"
|
||||
#include <QPropertyAnimation>
|
||||
#include <QScreen>
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
ToastWidget::ToastWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint | Qt::Tool);// 无边框 无任务栏
|
||||
setAttribute(Qt::WA_TranslucentBackground, true); // 背景透明
|
||||
}
|
||||
|
||||
ToastWidget::~ToastWidget()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ToastWidget::setText(const QString& text)
|
||||
{
|
||||
ui.label->setText(text);
|
||||
}
|
||||
|
||||
void ToastWidget::showAnimation(int timeout /*= 2000*/)
|
||||
{
|
||||
// 开始动画
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
|
||||
animation->setDuration(1000);
|
||||
animation->setStartValue(0);
|
||||
animation->setEndValue(1);
|
||||
animation->start();
|
||||
show();
|
||||
|
||||
QTimer::singleShot(timeout, [&]
|
||||
{
|
||||
// 结束动画
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
|
||||
animation->setDuration(1000);
|
||||
animation->setStartValue(1);
|
||||
animation->setEndValue(0);
|
||||
animation->start();
|
||||
connect(animation, &QPropertyAnimation::finished, [&]
|
||||
{
|
||||
close();
|
||||
deleteLater();// 关闭后析构
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ToastWidget::showTip(const QString& text, QWidget* parent /*= nullptr*/)
|
||||
{
|
||||
ToastWidget* toast = new ToastWidget(parent);
|
||||
toast->setWindowFlags(toast->windowFlags() | Qt::WindowStaysOnTopHint); // 置顶
|
||||
toast->setText(text);
|
||||
toast->setStyleSheet("font:bold;font-size:24px;color:rgb(255,255,255);");
|
||||
toast->adjustSize(); //设置完文本后调整下大小
|
||||
// 测试显示位于主屏的70%高度位置
|
||||
qDebug()<<parent->geometry();
|
||||
toast->move((parent->geometry().x() + (parent->size().width() - toast->width()) / 2),
|
||||
parent->geometry().y() + (parent->size().height() * 5 / 10));
|
||||
toast->showAnimation(100);
|
||||
}
|
||||
|
||||
void ToastWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter paint(this);
|
||||
paint.begin(this);
|
||||
auto kBackgroundColor = QColor(255, 255, 255);
|
||||
kBackgroundColor.setAlpha(0.1);// 透明度为0
|
||||
paint.setRenderHint(QPainter::Antialiasing, true);
|
||||
paint.setPen(Qt::NoPen);
|
||||
paint.setBrush(QBrush(kBackgroundColor, Qt::SolidPattern));//设置画刷形式
|
||||
paint.drawRect(0, 0, width(), height());
|
||||
paint.end();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#ifndef __TOAST__
|
||||
#define __TOAST__
|
||||
|
||||
#include <QWidget>
|
||||
#include "ui_toast.h"
|
||||
|
||||
class ToastWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToastWidget(QWidget *parent = Q_NULLPTR);
|
||||
~ToastWidget();
|
||||
|
||||
void setText(const QString& text);
|
||||
|
||||
void showAnimation(int timeout = 2000);// 动画方式show出,默认2秒后消失
|
||||
|
||||
public:
|
||||
// 静态调用
|
||||
static void showTip(const QString& text, QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event);
|
||||
|
||||
private:
|
||||
Ui::Form ui;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>932</width>
|
||||
<height>59</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>170</x>
|
||||
<y>10</y>
|
||||
<width>231</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,4 @@
|
|||
[requires]
|
||||
ffmpeg/4.2.1
|
||||
[imports]
|
||||
.,* -> ./third/msvc32 @ folder=True, ignore_case=True, excludes=*.html *.jpeg
|
|
@ -0,0 +1,422 @@
|
|||
#include "CPlayWidget.h"
|
||||
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QMouseEvent>
|
||||
#include "CPlayWidget.h"
|
||||
|
||||
|
||||
// 顶点着色器源码
|
||||
|
||||
const char *vsrcyuv = "attribute vec4 vertexIn; \
|
||||
attribute vec2 textureIn; \
|
||||
varying vec2 textureOut; \
|
||||
void main(void) \
|
||||
{ \
|
||||
gl_Position = vertexIn; \
|
||||
textureOut = textureIn; \
|
||||
}";
|
||||
|
||||
|
||||
// 片段着色器源码
|
||||
|
||||
const char *fsrcyuv = "varying vec2 textureOut; \
|
||||
uniform sampler2D tex_y; \
|
||||
uniform sampler2D tex_u; \
|
||||
uniform sampler2D tex_v; \
|
||||
void main(void) \
|
||||
{ \
|
||||
vec3 yuv; \
|
||||
vec3 rgb; \
|
||||
yuv.x = texture2D(tex_y, textureOut).r; \
|
||||
yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
|
||||
yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
|
||||
rgb = mat3( 1, 1, 1, \
|
||||
0, -0.39465, 2.03211, \
|
||||
1.13983, -0.58060, 0) * yuv; \
|
||||
gl_FragColor = vec4(rgb, 1); \
|
||||
}";
|
||||
|
||||
// rgb片段着色器源码
|
||||
// 注意MEDIASUBTYPE_RGB32 是bgr的,所以需要再进行一次转换
|
||||
|
||||
|
||||
const char *fsrcrgb = "varying vec2 textureOut; \
|
||||
uniform sampler2D rgbdata; \
|
||||
void main() \
|
||||
{ \
|
||||
gl_FragColor = texture(rgbdata, textureOut); \
|
||||
}";
|
||||
|
||||
void CPlayWidget::OnUpdateFrame() {
|
||||
this->PlayOneFrame();
|
||||
}
|
||||
|
||||
void CPlayWidget::OnPaintData(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
if(nullptr == m_pBufYuv420p)
|
||||
{
|
||||
m_pBufYuv420p = new unsigned char[len];
|
||||
qDebug("CPlayWidget::PlayOneFrame new data memory. Len=%d width=%d height=%d\n",
|
||||
len, m_nVideoW, m_nVideoW);
|
||||
memcpy(m_pBufYuv420p, data,len);
|
||||
//刷新界面,触发paintGL接口
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
CPlayWidget::CPlayWidget(QWidget *parent):QOpenGLWidget(parent) {
|
||||
textureUniformY = 0;
|
||||
textureUniformU = 0;
|
||||
textureUniformV = 0;
|
||||
id_y = 0;
|
||||
id_u = 0;
|
||||
id_v = 0;
|
||||
m_pTextureRGB = nullptr;
|
||||
m_pBufYuv420p = nullptr;
|
||||
m_pVSHader = NULL;
|
||||
m_pFSHader = NULL;
|
||||
m_pShaderProgram = NULL;
|
||||
m_pTextureY = NULL;
|
||||
m_pTextureU = NULL;
|
||||
m_pTextureV = NULL;
|
||||
m_pYuvFile = NULL;
|
||||
m_nVideoH = 0;
|
||||
m_nVideoW = 0;
|
||||
mType = TYPE_YUV420P;
|
||||
connect(&this->tm,SIGNAL(timeout()),this,SLOT(OnUpdateFrame()));
|
||||
//tm.start(1000);
|
||||
}
|
||||
|
||||
CPlayWidget::~CPlayWidget() {
|
||||
}
|
||||
|
||||
void CPlayWidget::PlayOneFrame() {//函数功能读取一张yuv图像数据进行显示,每单击一次,就显示一张图片
|
||||
if(NULL == m_pYuvFile)
|
||||
{
|
||||
//打开yuv视频文件 注意修改文件路径
|
||||
// m_pYuvFile = fopen("F://OpenglYuvDemo//1920_1080.yuv", "rb");
|
||||
m_pYuvFile = fopen("F://md_sample_sp420_1080p.yuv", "rb");
|
||||
//根据yuv视频数据的分辨率设置宽高,demo当中是1080p,这个地方要注意跟实际数据分辨率对应上
|
||||
// m_nVideoW = 1920;
|
||||
// m_nVideoH = 1080;
|
||||
}
|
||||
//申请内存存一帧yuv图像数据,其大小为分辨率的1.5倍
|
||||
|
||||
|
||||
int nLen = m_nVideoW*m_nVideoH*3/2;
|
||||
if(nullptr == m_pBufYuv420p)
|
||||
{
|
||||
m_pBufYuv420p = new unsigned char[nLen];
|
||||
qDebug("CPlayWidget::PlayOneFrame new data memory. Len=%d width=%d height=%d\n",
|
||||
nLen, m_nVideoW, m_nVideoW);
|
||||
}
|
||||
//将一帧yuv图像读到内存中
|
||||
|
||||
if(NULL == m_pYuvFile)
|
||||
{
|
||||
qFatal("read yuv file err.may be path is wrong!\n");
|
||||
return;
|
||||
}
|
||||
fread(m_pBufYuv420p, 1, nLen, m_pYuvFile);
|
||||
//刷新界面,触发paintGL接口
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
int CPlayWidget::SetDataType(CPlayWidget::IMG_TYPE type){
|
||||
this->mType = type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CPlayWidget::OnCameraData(uint8_t *dat, uint32_t size)
|
||||
{
|
||||
memcpy(this->m_pBufRgb32,dat,size);
|
||||
update();
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int CPlayWidget::SetImgSize(uint32_t width, uint32_t height)
|
||||
{
|
||||
m_nVideoH = height;
|
||||
m_nVideoW = width;
|
||||
if(mType == TYPE_RGB32){
|
||||
m_pBufRgb32 = new uint8_t[width * height *4];
|
||||
}
|
||||
if(mType == TYPE_YUV420P){
|
||||
m_pBufYuv420p = new uint8_t[width * height *3/2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
* Y = 0.299 R + 0.587 G + 0.114 B
|
||||
|
||||
U = - 0.1687 R - 0.3313 G + 0.5 B + 128
|
||||
|
||||
V = 0.5 R - 0.4187 G - 0.0813 B + 128
|
||||
|
||||
反过来,RGB 也可以直接从YUV (256级别) 计算:
|
||||
|
||||
R = Y + 1.402 (Cr-128)
|
||||
|
||||
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
|
||||
|
||||
B = Y + 1.772 (Cb-128)
|
||||
*/
|
||||
void CPlayWidget::initializeGL()
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
//现代opengl渲染管线依赖着色器来处理传入的数据
|
||||
//着色器:就是使用openGL着色语言(OpenGL Shading Language, GLSL)编写的一个小函数,
|
||||
// GLSL是构成所有OpenGL着色器的语言,具体的GLSL语言的语法需要读者查找相关资料
|
||||
//初始化顶点着色器 对象
|
||||
|
||||
m_pVSHader = new QOpenGLShader(QOpenGLShader::Vertex, this);
|
||||
|
||||
//编译顶点着色器程序
|
||||
bool bCompile = m_pVSHader->compileSourceCode(vsrcyuv);
|
||||
if(!bCompile)
|
||||
{
|
||||
// todo 设置错误状态
|
||||
}
|
||||
//初始化片段着色器 功能gpu中yuv转换成rgb
|
||||
m_pFSHader = new QOpenGLShader(QOpenGLShader::Fragment, this);
|
||||
if(mType == TYPE_RGB32){
|
||||
bCompile = m_pFSHader->compileSourceCode(fsrcrgb);
|
||||
}
|
||||
if(mType == TYPE_YUV420P){
|
||||
bCompile = m_pFSHader->compileSourceCode(fsrcyuv);
|
||||
}
|
||||
if(!bCompile)
|
||||
{
|
||||
// todo 设置错误状态
|
||||
}
|
||||
#define PROGRAM_VERTEX_ATTRIBUTE 0
|
||||
#define PROGRAM_TEXCOORD_ATTRIBUTE 1
|
||||
//创建着色器程序容器
|
||||
|
||||
m_pShaderProgram = new QOpenGLShaderProgram;
|
||||
//将片段着色器添加到程序容器
|
||||
|
||||
m_pShaderProgram->addShader(m_pFSHader);
|
||||
//将顶点着色器添加到程序容器
|
||||
|
||||
m_pShaderProgram->addShader(m_pVSHader);
|
||||
//绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明
|
||||
|
||||
m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX);
|
||||
//绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明
|
||||
|
||||
m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE);
|
||||
//链接所有所有添入到的着色器程序
|
||||
|
||||
m_pShaderProgram->link();
|
||||
|
||||
//激活所有链接
|
||||
|
||||
m_pShaderProgram->bind();
|
||||
|
||||
if(this->mType == TYPE_YUV420P){
|
||||
initShaderYuv();
|
||||
}
|
||||
if(this->mType == TYPE_RGB32){
|
||||
initShaderRgb();
|
||||
}
|
||||
glClearColor(0.0,0.0,0.0,0.0);//设置背景色
|
||||
}
|
||||
|
||||
|
||||
void CPlayWidget::resizeGL(int w, int h)
|
||||
{
|
||||
if(h == 0)// 防止被零除
|
||||
{
|
||||
h = 1;// 将高设为1
|
||||
}
|
||||
//设置视口
|
||||
glViewport(0,0, w,h);
|
||||
}
|
||||
|
||||
void CPlayWidget::paintGL()
|
||||
{
|
||||
if(mType == TYPE_YUV420P)
|
||||
loadYuvTexture();
|
||||
if(mType == TYPE_RGB32){
|
||||
loadRgbTexture();
|
||||
}
|
||||
//使用顶点数组方式绘制图形
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
void CPlayWidget::initShaderYuv()
|
||||
{
|
||||
//读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在
|
||||
//片段着色器源码中可以看到
|
||||
textureUniformY = m_pShaderProgram->uniformLocation("tex_y");
|
||||
textureUniformU = m_pShaderProgram->uniformLocation("tex_u");
|
||||
textureUniformV = m_pShaderProgram->uniformLocation("tex_v");
|
||||
// 顶点矩阵
|
||||
static const GLfloat vertexVertices[] = {
|
||||
-1.0f, -1.0f,
|
||||
1.0f, -1.0f,
|
||||
-1.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
//纹理矩阵
|
||||
static const GLfloat textureVertices[] = {
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
};
|
||||
//设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
|
||||
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
|
||||
//设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
|
||||
glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
|
||||
//启用ATTRIB_VERTEX属性的数据,默认是关闭的
|
||||
glEnableVertexAttribArray(ATTRIB_VERTEX);
|
||||
//启用ATTRIB_TEXTURE属性的数据,默认是关闭的
|
||||
glEnableVertexAttribArray(ATTRIB_TEXTURE);
|
||||
//分别创建y,u,v纹理对象
|
||||
m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
m_pTextureY->create();
|
||||
m_pTextureU->create();
|
||||
m_pTextureV->create();
|
||||
//获取返回y分量的纹理索引值
|
||||
id_y = m_pTextureY->textureId();
|
||||
//获取返回u分量的纹理索引值
|
||||
id_u = m_pTextureU->textureId();
|
||||
//获取返回v分量的纹理索引值
|
||||
id_v = m_pTextureV->textureId();
|
||||
}
|
||||
|
||||
void CPlayWidget::initShaderRgb()
|
||||
{
|
||||
//读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在
|
||||
//片段着色器源码中可以看到
|
||||
textureUniformRGB = m_pShaderProgram->uniformLocation("rgbdata");
|
||||
// 顶点矩阵
|
||||
static const GLfloat vertexVertices[] = {
|
||||
-1.0f, -1.0f,
|
||||
1.0f, -1.0f,
|
||||
-1.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
|
||||
//纹理矩阵
|
||||
|
||||
static const GLfloat textureVertices[] = {
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
//设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
|
||||
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
|
||||
//设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
|
||||
glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
|
||||
//启用ATTRIB_VERTEX属性的数据,默认是关闭的
|
||||
glEnableVertexAttribArray(ATTRIB_VERTEX);
|
||||
//启用ATTRIB_TEXTURE属性的数据,默认是关闭的
|
||||
glEnableVertexAttribArray(ATTRIB_TEXTURE);
|
||||
//分别创建y,u,v纹理对象
|
||||
m_pTextureRGB = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
m_pTextureRGB->create();
|
||||
//获取返回y分量的纹理索引值
|
||||
id_rgb = m_pTextureRGB->textureId();
|
||||
}
|
||||
|
||||
int CPlayWidget::loadYuvTexture()
|
||||
{
|
||||
//加载y数据纹理
|
||||
//激活纹理单元GL_TEXTURE0
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
//使用来自y数据生成纹理
|
||||
glBindTexture(GL_TEXTURE_2D, id_y);
|
||||
//使用内存中m_pBufYuv420p数据创建真正的y数据纹理
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RED,
|
||||
m_nVideoW,
|
||||
m_nVideoH,
|
||||
0,
|
||||
GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
m_pBufYuv420p);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
//加载u数据纹理
|
||||
glActiveTexture(GL_TEXTURE1);//激活纹理单元GL_TEXTURE1
|
||||
glBindTexture(GL_TEXTURE_2D, id_u);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RED,
|
||||
m_nVideoW/2,
|
||||
m_nVideoH/2,
|
||||
0,
|
||||
GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(char*)m_pBufYuv420p+m_nVideoW*m_nVideoH);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
//加载v数据纹理
|
||||
glActiveTexture(GL_TEXTURE2);//激活纹理单元GL_TEXTURE2
|
||||
glBindTexture(GL_TEXTURE_2D, id_v);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RED,
|
||||
m_nVideoW/2,
|
||||
m_nVideoH/2,
|
||||
0, GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(char*)m_pBufYuv420p+m_nVideoW*m_nVideoH*5/4);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
//指定y纹理要使用新值 只能用0,1,2等表示纹理单元的索引,这是opengl不人性化的地方
|
||||
//0对应纹理单元GL_TEXTURE0 1对应纹理单元GL_TEXTURE1 2对应纹理的单元
|
||||
glUniform1i(textureUniformY, 0);
|
||||
//指定u纹理要使用新值
|
||||
glUniform1i(textureUniformU, 1);
|
||||
//指定v纹理要使用新值
|
||||
glUniform1i(textureUniformV, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CPlayWidget::loadRgbTexture()
|
||||
{
|
||||
//加载rgb数据纹理
|
||||
//激活纹理单元GL_TEXTURE0
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
//使用来自y数据生成纹理
|
||||
glBindTexture(GL_TEXTURE_2D, id_rgb);
|
||||
//使用内存中m_pBufYuv420p数据创建真正的y数据纹理
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA,
|
||||
m_nVideoW,
|
||||
m_nVideoH,
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
m_pBufRgb32);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glUniform1i(textureUniformRGB, 0);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#ifndef GLPLAYWIDGET_H
|
||||
#define GLPLAYWIDGET_H
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QFile>
|
||||
#include "media/CameraCapture.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#define ATTRIB_VERTEX 3
|
||||
#define ATTRIB_TEXTURE 4
|
||||
|
||||
|
||||
|
||||
class CPlayWidget:public QOpenGLWidget,protected QOpenGLFunctions,public Camera::CameraObserver
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void OnUpdateFrame();
|
||||
void OnPaintData(const uint8_t *data,uint32_t len);
|
||||
public:
|
||||
typedef enum{
|
||||
TYPE_YUV420P,
|
||||
TYPE_RGB32,
|
||||
}IMG_TYPE;
|
||||
CPlayWidget(QWidget* parent);
|
||||
~CPlayWidget();
|
||||
void PlayOneFrame();
|
||||
int SetDataType(IMG_TYPE);
|
||||
int OnCameraData(uint8_t *dat, uint32_t size) override;
|
||||
int SetImgSize(uint32_t width,uint32_t );
|
||||
protected:
|
||||
QTimer tm;
|
||||
void initializeGL() override;
|
||||
void resizeGL(int w, int h) override;
|
||||
void paintGL() override;
|
||||
private:
|
||||
IMG_TYPE mType; // 目前只支持到RGB32,YUV420P
|
||||
GLuint textureUniformY; //y纹理数据位置
|
||||
GLuint textureUniformU; //u纹理数据位置
|
||||
GLuint textureUniformV; //v纹理数据位置
|
||||
GLuint textureUniformRGB; //RGB纹理位置
|
||||
|
||||
|
||||
GLuint textureUnifromRGB; //rgb32 的纹理位置
|
||||
|
||||
GLuint id_rgb;
|
||||
GLuint id_y;
|
||||
GLuint id_u;
|
||||
GLuint id_v; //v纹理对象ID
|
||||
|
||||
QOpenGLTexture* m_pTextureRGB; //RGB 纹理是一整块的
|
||||
|
||||
QOpenGLTexture* m_pTextureY; //y纹理对象
|
||||
QOpenGLTexture* m_pTextureU; //u纹理对象
|
||||
QOpenGLTexture* m_pTextureV; //v纹理对象
|
||||
QOpenGLShader *m_pVSHader; //顶点着色器程序对象
|
||||
QOpenGLShader *m_pFSHader; //片段着色器对象
|
||||
QOpenGLShaderProgram *m_pShaderProgram; //着色器程序容器
|
||||
int m_nVideoW; //视频分辨率宽
|
||||
int m_nVideoH; //视频分辨率高
|
||||
unsigned char *m_pBufYuv420p;
|
||||
unsigned char* m_pBufRgb32;
|
||||
|
||||
FILE* m_pYuvFile;
|
||||
|
||||
void initShaderYuv();
|
||||
void initShaderRgb();
|
||||
|
||||
int loadYuvTexture();
|
||||
int loadRgbTexture();
|
||||
};
|
||||
#endif
|
|
@ -0,0 +1,4 @@
|
|||
[requires]
|
||||
ffmpeg/4.2.1
|
||||
[imports]
|
||||
.,* -> ./third/msvc32 @ folder=True, ignore_case=True, excludes=*.html *.jpeg
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
const unsigned char Base64IdxTab[128] =
|
||||
{
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,62, 255,255,255,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,255,255, 255,255,255,255,
|
||||
255,0,1,2, 3,4,5,6, 7,8,9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,255, 255,255,255,255,
|
||||
255,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,255, 255,255,255,255
|
||||
};
|
||||
|
||||
#define BVal(x) Base64IdxTab[x]
|
||||
|
||||
int DecodeBase64(char * pInput, char * pOutput);
|
||||
|
||||
const char Base64ValTab[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
#define AVal(x) Base64ValTab[x]
|
||||
|
||||
int EncodeBase64(unsigned char * pInput, int iInputLen, unsigned char * pOutput);
|
||||
|
||||
#define DCD_ONCE_LEN 400*1024
|
||||
#define CDC_ONCE_LEN 300*1024
|
|
@ -0,0 +1,181 @@
|
|||
// BitmapEx.h: interface for the CBitmapEx class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_BITMAPEX_H__80F20A52_B43F_42C5_B182_AC8D27BF5C0E__INCLUDED_)
|
||||
#define AFX_BITMAPEX_H__80F20A52_B43F_42C5_B182_AC8D27BF5C0E__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
|
||||
#define _PI 3.1415926f // Value of PI
|
||||
#define _BITS_PER_PIXEL_32 32 // 32-bit color depth
|
||||
#define _BITS_PER_PIXEL_24 24 // 24-bit color depth
|
||||
#define _PIXEL DWORD // Pixel
|
||||
#define _RGB(r,g,b) (((r) << 16) | ((g) << 8) | (b)) // Convert to RGB
|
||||
#define _GetRValue(c) ((BYTE)(((c) & 0x00FF0000) >> 16)) // Red color component
|
||||
#define _GetGValue(c) ((BYTE)(((c) & 0x0000FF00) >> 8)) // Green color component
|
||||
#define _GetBValue(c) ((BYTE)((c) & 0x000000FF)) // Blue color component
|
||||
|
||||
|
||||
typedef long fixed; // Our new fixed point type
|
||||
#define itofx(x) ((x) << 8) // Integer to fixed point
|
||||
#define ftofx(x) (long)((x) * 256) // Float to fixed point
|
||||
#define dtofx(x) (long)((x) * 256) // Double to fixed point
|
||||
#define fxtoi(x) ((x) >> 8) // Fixed point to integer
|
||||
#define fxtof(x) ((float) (x) / 256) // Fixed point to float
|
||||
#define fxtod(x) ((double)(x) / 256) // Fixed point to double
|
||||
#define Mulfx(x,y) (((x) * (y)) >> 8) // Multiply a fixed by a fixed
|
||||
#define Divfx(x,y) (((x) << 8) / (y)) // Divide a fixed by a fixed
|
||||
|
||||
|
||||
typedef struct __POINT
|
||||
{
|
||||
long x;
|
||||
long y;
|
||||
|
||||
} _POINT, *_LPPOINT;
|
||||
|
||||
typedef struct __QUAD
|
||||
{
|
||||
_POINT p1;
|
||||
_POINT p2;
|
||||
_POINT p3;
|
||||
_POINT p4;
|
||||
|
||||
} _QUAD, *_LPQUAD;
|
||||
|
||||
typedef enum __RESAMPLE_MODE
|
||||
{
|
||||
RM_NEARESTNEIGHBOUR = 0,
|
||||
RM_BILINEAR,
|
||||
RM_BICUBIC,
|
||||
|
||||
} _RESAMPLE_MODE;
|
||||
|
||||
typedef enum __GRADIENT_MODE
|
||||
{
|
||||
GM_NONE = 0x00,
|
||||
GM_HORIZONTAL = 0x01,
|
||||
GM_VERTICAL = 0x02,
|
||||
GM_RADIAL = 0x04
|
||||
|
||||
} _GRADIENT_MODE;
|
||||
|
||||
|
||||
class CBitmapEx
|
||||
{
|
||||
public:
|
||||
// Public methods
|
||||
CBitmapEx();
|
||||
virtual ~CBitmapEx();
|
||||
void Create(long width, long height);
|
||||
void Create(CBitmapEx& bitmapEx);
|
||||
void Load(LPTSTR lpszBitmapFile);
|
||||
void Load(LPBYTE lpBitmapData);
|
||||
void Save(LPTSTR lpszBitmapFile);
|
||||
void Save(LPBYTE lpBitmapData);
|
||||
void Scale(long horizontalPercent=100, long verticalPercent=100);
|
||||
void Rotate(long degrees=0, _PIXEL bgColor=_RGB(0,0,0));
|
||||
void FlipHorizontal();
|
||||
void FlipVertical();
|
||||
void MirrorLeft();
|
||||
void MirrorRight();
|
||||
void MirrorTop();
|
||||
void MirrorBottom();
|
||||
void Clear(_PIXEL clearColor=_RGB(0,0,0));
|
||||
void Negative();
|
||||
void Grayscale();
|
||||
void Sepia(long depth=34);
|
||||
void Emboss();
|
||||
void Engrave();
|
||||
void Pixelize(long size=4);
|
||||
void Draw(HDC hDC);
|
||||
void Draw(HDC hDC, long dstX, long dstY);
|
||||
void Draw(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx, long srcX, long srcY);
|
||||
void Draw(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx, long srcX, long srcY, long alpha);
|
||||
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx);
|
||||
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha);
|
||||
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
|
||||
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
|
||||
void Draw(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
|
||||
void Draw(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
|
||||
void DrawTransparent(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx, long srcX, long srcY, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx, long srcX, long srcY, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void DrawBlended(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx, long srcX, long srcY, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
|
||||
void DrawBlended(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
|
||||
LPBITMAPFILEHEADER GetFileInfo() {return &m_bfh;}
|
||||
LPBITMAPINFOHEADER GetInfo() {return &m_bih;}
|
||||
long GetWidth() {return m_bih.biWidth;}
|
||||
long GetHeight() {return m_bih.biHeight;}
|
||||
long GetPitch() {return m_iPitch;}
|
||||
long GetBpp() {return m_iBpp;}
|
||||
long GetPaletteEntries() {return m_iPaletteEntries;}
|
||||
LPRGBQUAD GetPalette() {return m_lpPalette;}
|
||||
DWORD GetSize() {return m_dwSize;}
|
||||
LPBYTE GetData() {return m_lpData;}
|
||||
void SetResampleMode(_RESAMPLE_MODE mode=RM_NEARESTNEIGHBOUR) {m_ResampleMode = mode;}
|
||||
_RESAMPLE_MODE GetResampleMode() {return m_ResampleMode;}
|
||||
BOOL IsValid() {return (m_dwSize > 0);}
|
||||
_PIXEL GetPixel(long x, long y);
|
||||
void SetPixel(long x, long y, _PIXEL pixel);
|
||||
|
||||
//wangjun
|
||||
void LoadImageFile(LPTSTR lpszImageFile);
|
||||
void SaveJPGFile(LPTSTR lpszImageFile);
|
||||
|
||||
private:
|
||||
// Private methods
|
||||
void _ConvertTo32Bpp();
|
||||
void _ConvertTo24Bpp();
|
||||
void _ScaleNearestNeighbour(long horizontalPercent, long verticalPercent);
|
||||
void _ScaleBilinear(long horizontalPercent, long verticalPercent);
|
||||
void _ScaleBicubic(long horizontalPercent, long verticalPercent);
|
||||
void _RotateNearestNeighbour(long degrees, _PIXEL bgColor);
|
||||
void _RotateBilinear(long degrees, _PIXEL bgColor);
|
||||
void _RotateBicubic(long degrees, _PIXEL bgColor);
|
||||
void _DrawNearestNeighbour(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
|
||||
void _DrawBilinear(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
|
||||
void _DrawBicubic(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
|
||||
void _DrawNearestNeighbour(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
|
||||
void _DrawBilinear(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
|
||||
void _DrawBicubic(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
|
||||
void _DrawTransparentNearestNeighbour(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawTransparentBilinear(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawTransparentBicubic(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawTransparentNearestNeighbour(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawTransparentBilinear(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawTransparentBicubic(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
|
||||
void _DrawBlendedNearestNeighbour(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
|
||||
void _DrawBlendedBilinear(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
|
||||
void _DrawBlendedBicubic(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
|
||||
|
||||
// wanhjun
|
||||
HANDLE _dibFromBitmap(HBITMAP hBitmap); //DDB->DIB
|
||||
int _DIBNumColors (LPBITMAPINFOHEADER lpbi);
|
||||
HBITMAP _extractBitmap(IPicture* pPicture);
|
||||
int _GetCodecClsid(const WCHAR* format, CLSID* pClsid);
|
||||
|
||||
private:
|
||||
// Private members
|
||||
BITMAPFILEHEADER m_bfh;
|
||||
BITMAPINFOHEADER m_bih;
|
||||
long m_iPaletteEntries;
|
||||
RGBQUAD m_lpPalette[256];
|
||||
long m_iPitch;
|
||||
long m_iBpp;
|
||||
DWORD m_dwSize;
|
||||
LPBYTE m_lpData;
|
||||
_RESAMPLE_MODE m_ResampleMode;
|
||||
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_BITMAPEX_H__80F20A52_B43F_42C5_B182_AC8D27BF5C0E__INCLUDED_)
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
#include <Unknwn.h>
|
||||
#include <strmif.h>
|
||||
|
||||
#pragma comment(lib, "strmiids.lib")
|
||||
|
||||
#ifndef __qedit_h__
|
||||
#define __qedit_h__
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct __declspec(uuid("0579154a-2b53-4994-b0d0-e773148eff85"))
|
||||
ISampleGrabberCB : IUnknown
|
||||
{
|
||||
//
|
||||
// Raw methods provided by interface
|
||||
//
|
||||
|
||||
virtual HRESULT __stdcall SampleCB(
|
||||
double SampleTime,
|
||||
struct IMediaSample * pSample) = 0;
|
||||
virtual HRESULT __stdcall BufferCB(
|
||||
double SampleTime,
|
||||
unsigned char * pBuffer,
|
||||
long BufferLen) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
struct __declspec(uuid("6b652fff-11fe-4fce-92ad-0266b5d7c78f"))
|
||||
ISampleGrabber : IUnknown
|
||||
{
|
||||
//
|
||||
// Raw methods provided by interface
|
||||
//
|
||||
|
||||
virtual HRESULT __stdcall SetOneShot(
|
||||
long OneShot) = 0;
|
||||
virtual HRESULT __stdcall SetMediaType(
|
||||
struct _AMMediaType * pType) = 0;
|
||||
virtual HRESULT __stdcall GetConnectedMediaType(
|
||||
struct _AMMediaType * pType) = 0;
|
||||
virtual HRESULT __stdcall SetBufferSamples(
|
||||
long BufferThem) = 0;
|
||||
virtual HRESULT __stdcall GetCurrentBuffer(
|
||||
/*[in,out]*/ long * pBufferSize,
|
||||
/*[out]*/ long * pBuffer) = 0;
|
||||
virtual HRESULT __stdcall GetCurrentSample(
|
||||
/*[out,retval]*/ struct IMediaSample * * ppSample) = 0;
|
||||
virtual HRESULT __stdcall SetCallback(
|
||||
struct ISampleGrabberCB * pCallback,
|
||||
long WhichMethodToCallback) = 0;
|
||||
};
|
||||
|
||||
|
||||
static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce,{ 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
|
||||
static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994,{ 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
|
||||
static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3,{ 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3,{ 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "guiddef.h"
|
||||
#include <dshow.h>
|
||||
#include <windows.h>
|
||||
#include "qedit.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
};
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
AVPixelFormat GUIDToAvFormat(GUID mediatype);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
VERSION=v2.4
|
||||
|
||||
prefix=/usr/local
|
||||
|
||||
incdir=$(prefix)/include/librtmp
|
||||
bindir=$(prefix)/bin
|
||||
libdir=$(prefix)/lib
|
||||
mandir=$(prefix)/man
|
||||
BINDIR=$(DESTDIR)$(bindir)
|
||||
INCDIR=$(DESTDIR)$(incdir)
|
||||
LIBDIR=$(DESTDIR)$(libdir)
|
||||
MANDIR=$(DESTDIR)$(mandir)
|
||||
|
||||
CC=$(CROSS_COMPILE)gcc
|
||||
LD=$(CROSS_COMPILE)ld
|
||||
AR=$(CROSS_COMPILE)ar
|
||||
|
||||
SYS=posix
|
||||
CRYPTO=OPENSSL
|
||||
#CRYPTO=GNUTLS
|
||||
DEF_POLARSSL=-DUSE_POLARSSL
|
||||
DEF_OPENSSL=-DUSE_OPENSSL
|
||||
DEF_GNUTLS=-DUSE_GNUTLS
|
||||
DEF_=-DNO_CRYPTO
|
||||
REQ_GNUTLS=gnutls,hogweed,nettle
|
||||
REQ_OPENSSL=libssl,libcrypto
|
||||
PUB_GNUTLS=-lgmp
|
||||
LIBZ=-lz
|
||||
LIBS_posix=
|
||||
LIBS_darwin=
|
||||
LIBS_mingw=-lws2_32 -lwinmm -lgdi32
|
||||
LIB_GNUTLS=-lgnutls -lhogweed -lnettle -lgmp $(LIBZ)
|
||||
LIB_OPENSSL=-lssl -lcrypto $(LIBZ)
|
||||
LIB_POLARSSL=-lpolarssl $(LIBZ)
|
||||
PRIVATE_LIBS=$(LIBS_$(SYS))
|
||||
CRYPTO_LIB=$(LIB_$(CRYPTO)) $(PRIVATE_LIBS)
|
||||
CRYPTO_REQ=$(REQ_$(CRYPTO))
|
||||
CRYPTO_DEF=$(DEF_$(CRYPTO))
|
||||
PUBLIC_LIBS=$(PUB_$(CRYPTO))
|
||||
|
||||
SO_VERSION=1
|
||||
SOX_posix=so
|
||||
SOX_darwin=dylib
|
||||
SOX_mingw=dll
|
||||
SOX=$(SOX_$(SYS))
|
||||
SO_posix=.$(SOX).$(SO_VERSION)
|
||||
SO_darwin=.$(SO_VERSION).$(SOX)
|
||||
SO_mingw=-$(SO_VERSION).$(SOX)
|
||||
SO_EXT=$(SO_$(SYS))
|
||||
|
||||
SODIR_posix=$(LIBDIR)
|
||||
SODIR_darwin=$(LIBDIR)
|
||||
SODIR_mingw=$(BINDIR)
|
||||
SODIR=$(SODIR_$(SYS))
|
||||
|
||||
SO_LDFLAGS_posix=-shared -Wl,-soname,$@
|
||||
SO_LDFLAGS_darwin=-dynamiclib -twolevel_namespace -undefined dynamic_lookup \
|
||||
-fno-common -headerpad_max_install_names -install_name $(libdir)/$@
|
||||
SO_LDFLAGS_mingw=-shared -Wl,--out-implib,librtmp.dll.a
|
||||
SO_LDFLAGS=$(SO_LDFLAGS_$(SYS))
|
||||
|
||||
INSTALL_IMPLIB_posix=
|
||||
INSTALL_IMPLIB_darwin=
|
||||
INSTALL_IMPLIB_mingw=cp librtmp.dll.a $(LIBDIR)
|
||||
INSTALL_IMPLIB=$(INSTALL_IMPLIB_$(SYS))
|
||||
|
||||
SHARED=yes
|
||||
SODEF_yes=-fPIC
|
||||
SOLIB_yes=librtmp$(SO_EXT)
|
||||
SOINST_yes=install_so
|
||||
SO_DEF=$(SODEF_$(SHARED))
|
||||
SO_LIB=$(SOLIB_$(SHARED))
|
||||
SO_INST=$(SOINST_$(SHARED))
|
||||
|
||||
DEF=-DRTMPDUMP_VERSION=\"$(VERSION)\" $(CRYPTO_DEF) $(XDEF)
|
||||
OPT=-O2
|
||||
CFLAGS=-Wall $(XCFLAGS) $(INC) $(DEF) $(OPT) $(SO_DEF)
|
||||
LDFLAGS=$(XLDFLAGS)
|
||||
|
||||
|
||||
OBJS=rtmp.o log.o amf.o hashswf.o parseurl.o
|
||||
|
||||
all: librtmp.a $(SO_LIB)
|
||||
|
||||
clean:
|
||||
rm -f *.o *.a *.$(SOX) *$(SO_EXT) librtmp.pc
|
||||
|
||||
librtmp.a: $(OBJS)
|
||||
$(AR) rs $@ $?
|
||||
|
||||
librtmp$(SO_EXT): $(OBJS)
|
||||
$(CC) $(SO_LDFLAGS) $(LDFLAGS) -o $@ $^ $> $(CRYPTO_LIB)
|
||||
ln -sf $@ librtmp.$(SOX)
|
||||
|
||||
log.o: log.c log.h Makefile
|
||||
rtmp.o: rtmp.c rtmp.h rtmp_sys.h handshake.h dh.h log.h amf.h Makefile
|
||||
amf.o: amf.c amf.h bytes.h log.h Makefile
|
||||
hashswf.o: hashswf.c http.h rtmp.h rtmp_sys.h Makefile
|
||||
parseurl.o: parseurl.c rtmp.h rtmp_sys.h log.h Makefile
|
||||
|
||||
librtmp.pc: librtmp.pc.in Makefile
|
||||
sed -e "s;@prefix@;$(prefix);" -e "s;@libdir@;$(libdir);" \
|
||||
-e "s;@VERSION@;$(VERSION);" \
|
||||
-e "s;@CRYPTO_REQ@;$(CRYPTO_REQ);" \
|
||||
-e "s;@PUBLIC_LIBS@;$(PUBLIC_LIBS);" \
|
||||
-e "s;@PRIVATE_LIBS@;$(PRIVATE_LIBS);" librtmp.pc.in > $@
|
||||
|
||||
install: install_base $(SO_INST)
|
||||
|
||||
install_base: librtmp.a librtmp.pc
|
||||
-mkdir -p $(INCDIR) $(LIBDIR)/pkgconfig $(MANDIR)/man3 $(SODIR)
|
||||
cp amf.h http.h log.h rtmp.h $(INCDIR)
|
||||
cp librtmp.a $(LIBDIR)
|
||||
cp librtmp.pc $(LIBDIR)/pkgconfig
|
||||
cp librtmp.3 $(MANDIR)/man3
|
||||
|
||||
install_so: librtmp$(SO_EXT)
|
||||
cp librtmp$(SO_EXT) $(SODIR)
|
||||
$(INSTALL_IMPLIB)
|
||||
cd $(SODIR); ln -sf librtmp$(SO_EXT) librtmp.$(SOX)
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,164 @@
|
|||
#ifndef __AMF_H__
|
||||
#define __AMF_H__
|
||||
/*
|
||||
* Copyright (C) 2005-2008 Team XBMC
|
||||
* http://www.xbmc.org
|
||||
* Copyright (C) 2008-2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{ AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT,
|
||||
AMF_MOVIECLIP, /* reserved, not used */
|
||||
AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END,
|
||||
AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED,
|
||||
AMF_RECORDSET, /* reserved, not used */
|
||||
AMF_XML_DOC, AMF_TYPED_OBJECT,
|
||||
AMF_AVMPLUS, /* switch to AMF3 */
|
||||
AMF_INVALID = 0xff
|
||||
} AMFDataType;
|
||||
|
||||
typedef enum
|
||||
{ AMF3_UNDEFINED = 0, AMF3_NULL, AMF3_FALSE, AMF3_TRUE,
|
||||
AMF3_INTEGER, AMF3_DOUBLE, AMF3_STRING, AMF3_XML_DOC, AMF3_DATE,
|
||||
AMF3_ARRAY, AMF3_OBJECT, AMF3_XML, AMF3_BYTE_ARRAY
|
||||
} AMF3DataType;
|
||||
|
||||
typedef struct AVal
|
||||
{
|
||||
char *av_val;
|
||||
int av_len;
|
||||
} AVal;
|
||||
#define AVC(str) {str,sizeof(str)-1}
|
||||
#define AVMATCH(a1,a2) ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len))
|
||||
|
||||
struct AMFObjectProperty;
|
||||
|
||||
typedef struct AMFObject
|
||||
{
|
||||
int o_num;
|
||||
struct AMFObjectProperty *o_props;
|
||||
} AMFObject;
|
||||
|
||||
typedef struct AMFObjectProperty
|
||||
{
|
||||
AVal p_name;
|
||||
AMFDataType p_type;
|
||||
union
|
||||
{
|
||||
double p_number;
|
||||
AVal p_aval;
|
||||
AMFObject p_object;
|
||||
} p_vu;
|
||||
int16_t p_UTCoffset;
|
||||
} AMFObjectProperty;
|
||||
|
||||
char *AMF_EncodeString(char *output, char *outend, const AVal * str);
|
||||
char *AMF_EncodeNumber(char *output, char *outend, double dVal);
|
||||
char *AMF_EncodeInt16(char *output, char *outend, short nVal);
|
||||
char *AMF_EncodeInt24(char *output, char *outend, int nVal);
|
||||
char *AMF_EncodeInt32(char *output, char *outend, int nVal);
|
||||
char *AMF_EncodeBoolean(char *output, char *outend, int bVal);
|
||||
|
||||
/* Shortcuts for AMFProp_Encode */
|
||||
char *AMF_EncodeNamedString(char *output, char *outend, const AVal * name, const AVal * value);
|
||||
char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal * name, double dVal);
|
||||
char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal * name, int bVal);
|
||||
|
||||
unsigned short AMF_DecodeInt16(const char *data);
|
||||
unsigned int AMF_DecodeInt24(const char *data);
|
||||
unsigned int AMF_DecodeInt32(const char *data);
|
||||
void AMF_DecodeString(const char *data, AVal * str);
|
||||
void AMF_DecodeLongString(const char *data, AVal * str);
|
||||
int AMF_DecodeBoolean(const char *data);
|
||||
double AMF_DecodeNumber(const char *data);
|
||||
|
||||
char *AMF_Encode(AMFObject * obj, char *pBuffer, char *pBufEnd);
|
||||
char *AMF_EncodeEcmaArray(AMFObject *obj, char *pBuffer, char *pBufEnd);
|
||||
char *AMF_EncodeArray(AMFObject *obj, char *pBuffer, char *pBufEnd);
|
||||
|
||||
int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize,
|
||||
int bDecodeName);
|
||||
int AMF_DecodeArray(AMFObject * obj, const char *pBuffer, int nSize,
|
||||
int nArrayLen, int bDecodeName);
|
||||
int AMF3_Decode(AMFObject * obj, const char *pBuffer, int nSize,
|
||||
int bDecodeName);
|
||||
void AMF_Dump(AMFObject * obj);
|
||||
void AMF_Reset(AMFObject * obj);
|
||||
|
||||
void AMF_AddProp(AMFObject * obj, const AMFObjectProperty * prop);
|
||||
int AMF_CountProp(AMFObject * obj);
|
||||
AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name,
|
||||
int nIndex);
|
||||
|
||||
AMFDataType AMFProp_GetType(AMFObjectProperty * prop);
|
||||
void AMFProp_SetNumber(AMFObjectProperty * prop, double dval);
|
||||
void AMFProp_SetBoolean(AMFObjectProperty * prop, int bflag);
|
||||
void AMFProp_SetString(AMFObjectProperty * prop, AVal * str);
|
||||
void AMFProp_SetObject(AMFObjectProperty * prop, AMFObject * obj);
|
||||
|
||||
void AMFProp_GetName(AMFObjectProperty * prop, AVal * name);
|
||||
void AMFProp_SetName(AMFObjectProperty * prop, AVal * name);
|
||||
double AMFProp_GetNumber(AMFObjectProperty * prop);
|
||||
int AMFProp_GetBoolean(AMFObjectProperty * prop);
|
||||
void AMFProp_GetString(AMFObjectProperty * prop, AVal * str);
|
||||
void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj);
|
||||
|
||||
int AMFProp_IsValid(AMFObjectProperty * prop);
|
||||
|
||||
char *AMFProp_Encode(AMFObjectProperty * prop, char *pBuffer, char *pBufEnd);
|
||||
int AMF3Prop_Decode(AMFObjectProperty * prop, const char *pBuffer,
|
||||
int nSize, int bDecodeName);
|
||||
int AMFProp_Decode(AMFObjectProperty * prop, const char *pBuffer,
|
||||
int nSize, int bDecodeName);
|
||||
|
||||
void AMFProp_Dump(AMFObjectProperty * prop);
|
||||
void AMFProp_Reset(AMFObjectProperty * prop);
|
||||
|
||||
typedef struct AMF3ClassDef
|
||||
{
|
||||
AVal cd_name;
|
||||
char cd_externalizable;
|
||||
char cd_dynamic;
|
||||
int cd_num;
|
||||
AVal *cd_props;
|
||||
} AMF3ClassDef;
|
||||
|
||||
void AMF3CD_AddProp(AMF3ClassDef * cd, AVal * prop);
|
||||
AVal *AMF3CD_GetProp(AMF3ClassDef * cd, int idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __AMF_H__ */
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2008 Team XBMC
|
||||
* http://www.xbmc.org
|
||||
* Copyright (C) 2008-2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#ifndef __BYTES_H__
|
||||
#define __BYTES_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows is little endian only */
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define __BIG_ENDIAN 4321
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#define __FLOAT_WORD_ORDER __BYTE_ORDER
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
|
||||
#else /* !_WIN32 */
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#if defined(BYTE_ORDER) && !defined(__BYTE_ORDER)
|
||||
#define __BYTE_ORDER BYTE_ORDER
|
||||
#endif
|
||||
|
||||
#if defined(BIG_ENDIAN) && !defined(__BIG_ENDIAN)
|
||||
#define __BIG_ENDIAN BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
#if defined(LITTLE_ENDIAN) && !defined(__LITTLE_ENDIAN)
|
||||
#define __LITTLE_ENDIAN LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#endif /* !_WIN32 */
|
||||
|
||||
/* define default endianness */
|
||||
#ifndef __LITTLE_ENDIAN
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#endif
|
||||
|
||||
#ifndef __BIG_ENDIAN
|
||||
#define __BIG_ENDIAN 4321
|
||||
#endif
|
||||
|
||||
#ifndef __BYTE_ORDER
|
||||
#warning "Byte order not defined on your system, assuming little endian!"
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
/* ok, we assume to have the same float word order and byte order if float word order is not defined */
|
||||
#ifndef __FLOAT_WORD_ORDER
|
||||
#warning "Float word order not defined, assuming the same as byte order!"
|
||||
#define __FLOAT_WORD_ORDER __BYTE_ORDER
|
||||
#endif
|
||||
|
||||
#if !defined(__BYTE_ORDER) || !defined(__FLOAT_WORD_ORDER)
|
||||
#error "Undefined byte or float word order!"
|
||||
#endif
|
||||
|
||||
#if __FLOAT_WORD_ORDER != __BIG_ENDIAN && __FLOAT_WORD_ORDER != __LITTLE_ENDIAN
|
||||
#error "Unknown/unsupported float word order!"
|
||||
#endif
|
||||
|
||||
#if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN
|
||||
#error "Unknown/unsupported byte order!"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
/* RTMPDump - Diffie-Hellmann Key Exchange
|
||||
* Copyright (C) 2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef USE_POLARSSL
|
||||
#include <polarssl/dhm.h>
|
||||
typedef mpi * MP_t;
|
||||
#define MP_new(m) m = malloc(sizeof(mpi)); mpi_init(m)
|
||||
#define MP_set_w(mpi, w) mpi_lset(mpi, w)
|
||||
#define MP_cmp(u, v) mpi_cmp_mpi(u, v)
|
||||
#define MP_set(u, v) mpi_copy(u, v)
|
||||
#define MP_sub_w(mpi, w) mpi_sub_int(mpi, mpi, w)
|
||||
#define MP_cmp_1(mpi) mpi_cmp_int(mpi, 1)
|
||||
#define MP_modexp(r, y, q, p) mpi_exp_mod(r, y, q, p, NULL)
|
||||
#define MP_free(mpi) mpi_free(mpi); free(mpi)
|
||||
#define MP_gethex(u, hex, res) MP_new(u); res = mpi_read_string(u, 16, hex) == 0
|
||||
#define MP_bytes(u) mpi_size(u)
|
||||
#define MP_setbin(u,buf,len) mpi_write_binary(u,buf,len)
|
||||
#define MP_getbin(u,buf,len) MP_new(u); mpi_read_binary(u,buf,len)
|
||||
|
||||
typedef struct MDH {
|
||||
MP_t p;
|
||||
MP_t g;
|
||||
MP_t pub_key;
|
||||
MP_t priv_key;
|
||||
long length;
|
||||
dhm_context ctx;
|
||||
} MDH;
|
||||
|
||||
#define MDH_new() calloc(1,sizeof(MDH))
|
||||
#define MDH_free(vp) {MDH *_dh = vp; dhm_free(&_dh->ctx); MP_free(_dh->p); MP_free(_dh->g); MP_free(_dh->pub_key); MP_free(_dh->priv_key); free(_dh);}
|
||||
|
||||
static int MDH_generate_key(MDH *dh)
|
||||
{
|
||||
unsigned char out[2];
|
||||
MP_set(&dh->ctx.P, dh->p);
|
||||
MP_set(&dh->ctx.G, dh->g);
|
||||
dh->ctx.len = 128;
|
||||
dhm_make_public(&dh->ctx, 1024, out, 1, havege_random, &RTMP_TLS_ctx->hs);
|
||||
MP_new(dh->pub_key);
|
||||
MP_new(dh->priv_key);
|
||||
MP_set(dh->pub_key, &dh->ctx.GX);
|
||||
MP_set(dh->priv_key, &dh->ctx.X);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int MDH_compute_key(uint8_t *secret, size_t len, MP_t pub, MDH *dh)
|
||||
{
|
||||
MP_set(&dh->ctx.GY, pub);
|
||||
dhm_calc_secret(&dh->ctx, secret, &len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined(USE_GNUTLS)
|
||||
#include <gmp.h>
|
||||
#include <nettle/bignum.h>
|
||||
#include <gnutls/crypto.h>
|
||||
typedef mpz_ptr MP_t;
|
||||
#define MP_new(m) m = malloc(sizeof(*m)); mpz_init2(m, 1)
|
||||
#define MP_set_w(mpi, w) mpz_set_ui(mpi, w)
|
||||
#define MP_cmp(u, v) mpz_cmp(u, v)
|
||||
#define MP_set(u, v) mpz_set(u, v)
|
||||
#define MP_sub_w(mpi, w) mpz_sub_ui(mpi, mpi, w)
|
||||
#define MP_cmp_1(mpi) mpz_cmp_ui(mpi, 1)
|
||||
#define MP_modexp(r, y, q, p) mpz_powm(r, y, q, p)
|
||||
#define MP_free(mpi) mpz_clear(mpi); free(mpi)
|
||||
#define MP_gethex(u, hex, res) u = malloc(sizeof(*u)); mpz_init2(u, 1); res = (mpz_set_str(u, hex, 16) == 0)
|
||||
#define MP_bytes(u) (mpz_sizeinbase(u, 2) + 7) / 8
|
||||
#define MP_setbin(u,buf,len) nettle_mpz_get_str_256(len,buf,u)
|
||||
#define MP_getbin(u,buf,len) u = malloc(sizeof(*u)); mpz_init2(u, 1); nettle_mpz_set_str_256_u(u,len,buf)
|
||||
|
||||
typedef struct MDH {
|
||||
MP_t p;
|
||||
MP_t g;
|
||||
MP_t pub_key;
|
||||
MP_t priv_key;
|
||||
long length;
|
||||
} MDH;
|
||||
|
||||
#define MDH_new() calloc(1,sizeof(MDH))
|
||||
#define MDH_free(dh) do {MP_free(((MDH*)(dh))->p); MP_free(((MDH*)(dh))->g); MP_free(((MDH*)(dh))->pub_key); MP_free(((MDH*)(dh))->priv_key); free(dh);} while(0)
|
||||
|
||||
static int MDH_generate_key(MDH *dh)
|
||||
{
|
||||
int num_bytes;
|
||||
uint32_t seed;
|
||||
gmp_randstate_t rs;
|
||||
|
||||
num_bytes = (mpz_sizeinbase(dh->p, 2) + 7) / 8 - 1;
|
||||
if (num_bytes <= 0 || num_bytes > 18000)
|
||||
return 0;
|
||||
|
||||
dh->priv_key = calloc(1, sizeof(*dh->priv_key));
|
||||
if (!dh->priv_key)
|
||||
return 0;
|
||||
mpz_init2(dh->priv_key, 1);
|
||||
gnutls_rnd(GNUTLS_RND_RANDOM, &seed, sizeof(seed));
|
||||
gmp_randinit_mt(rs);
|
||||
gmp_randseed_ui(rs, seed);
|
||||
mpz_urandomb(dh->priv_key, rs, num_bytes);
|
||||
gmp_randclear(rs);
|
||||
|
||||
dh->pub_key = calloc(1, sizeof(*dh->pub_key));
|
||||
if (!dh->pub_key)
|
||||
return 0;
|
||||
mpz_init2(dh->pub_key, 1);
|
||||
if (!dh->pub_key) {
|
||||
mpz_clear(dh->priv_key);
|
||||
free(dh->priv_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mpz_powm(dh->pub_key, dh->g, dh->priv_key, dh->p);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int MDH_compute_key(uint8_t *secret, size_t len, MP_t pub, MDH *dh)
|
||||
{
|
||||
mpz_ptr k;
|
||||
int num_bytes;
|
||||
|
||||
num_bytes = (mpz_sizeinbase(dh->p, 2) + 7) / 8;
|
||||
if (num_bytes <= 0 || num_bytes > 18000)
|
||||
return -1;
|
||||
|
||||
k = calloc(1, sizeof(*k));
|
||||
if (!k)
|
||||
return -1;
|
||||
mpz_init2(k, 1);
|
||||
|
||||
mpz_powm(k, pub, dh->priv_key, dh->p);
|
||||
nettle_mpz_get_str_256(len, secret, k);
|
||||
mpz_clear(k);
|
||||
free(k);
|
||||
|
||||
/* return the length of the shared secret key like DH_compute_key */
|
||||
return len;
|
||||
}
|
||||
|
||||
#else /* USE_OPENSSL */
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dh.h>
|
||||
|
||||
typedef BIGNUM * MP_t;
|
||||
#define MP_new(m) m = BN_new()
|
||||
#define MP_set_w(mpi, w) BN_set_word(mpi, w)
|
||||
#define MP_cmp(u, v) BN_cmp(u, v)
|
||||
#define MP_set(u, v) BN_copy(u, v)
|
||||
#define MP_sub_w(mpi, w) BN_sub_word(mpi, w)
|
||||
#define MP_cmp_1(mpi) BN_cmp(mpi, BN_value_one())
|
||||
#define MP_modexp(r, y, q, p) do {BN_CTX *ctx = BN_CTX_new(); BN_mod_exp(r, y, q, p, ctx); BN_CTX_free(ctx);} while(0)
|
||||
#define MP_free(mpi) BN_free(mpi)
|
||||
#define MP_gethex(u, hex, res) res = BN_hex2bn(&u, hex)
|
||||
#define MP_bytes(u) BN_num_bytes(u)
|
||||
#define MP_setbin(u,buf,len) BN_bn2bin(u,buf)
|
||||
#define MP_getbin(u,buf,len) u = BN_bin2bn(buf,len,0)
|
||||
|
||||
#define MDH DH
|
||||
#define MDH_new() DH_new()
|
||||
#define MDH_free(dh) DH_free(dh)
|
||||
#define MDH_generate_key(dh) DH_generate_key(dh)
|
||||
#define MDH_compute_key(secret, seclen, pub, dh) DH_compute_key(secret, pub, dh)
|
||||
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
#include "dhgroups.h"
|
||||
|
||||
/* RFC 2631, Section 2.1.5, http://www.ietf.org/rfc/rfc2631.txt */
|
||||
static int
|
||||
isValidPublicKey(MP_t y, MP_t p, MP_t q)
|
||||
{
|
||||
int ret = TRUE;
|
||||
MP_t bn;
|
||||
assert(y);
|
||||
|
||||
MP_new(bn);
|
||||
assert(bn);
|
||||
|
||||
/* y must lie in [2,p-1] */
|
||||
MP_set_w(bn, 1);
|
||||
if (MP_cmp(y, bn) < 0)
|
||||
{
|
||||
RTMP_Log(RTMP_LOGERROR, "DH public key must be at least 2");
|
||||
ret = FALSE;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* bn = p-2 */
|
||||
MP_set(bn, p);
|
||||
MP_sub_w(bn, 1);
|
||||
if (MP_cmp(y, bn) > 0)
|
||||
{
|
||||
RTMP_Log(RTMP_LOGERROR, "DH public key must be at most p-2");
|
||||
ret = FALSE;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* Verify with Sophie-Germain prime
|
||||
*
|
||||
* This is a nice test to make sure the public key position is calculated
|
||||
* correctly. This test will fail in about 50% of the cases if applied to
|
||||
* random data.
|
||||
*/
|
||||
if (q)
|
||||
{
|
||||
/* y must fulfill y^q mod p = 1 */
|
||||
MP_modexp(bn, y, q, p);
|
||||
|
||||
if (MP_cmp_1(bn) != 0)
|
||||
{
|
||||
RTMP_Log(RTMP_LOGWARNING, "DH public key does not fulfill y^q mod p = 1");
|
||||
}
|
||||
}
|
||||
|
||||
failed:
|
||||
MP_free(bn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static MDH *
|
||||
DHInit(int nKeyBits)
|
||||
{
|
||||
size_t res;
|
||||
MDH *dh = MDH_new();
|
||||
|
||||
if (!dh)
|
||||
goto failed;
|
||||
|
||||
MP_new(dh->g);
|
||||
|
||||
if (!dh->g)
|
||||
goto failed;
|
||||
|
||||
MP_gethex(dh->p, P1024, res); /* prime P1024, see dhgroups.h */
|
||||
if (!res)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
|
||||
MP_set_w(dh->g, 2); /* base 2 */
|
||||
|
||||
dh->length = nKeyBits;
|
||||
return dh;
|
||||
|
||||
failed:
|
||||
if (dh)
|
||||
MDH_free(dh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
DHGenerateKey(MDH *dh)
|
||||
{
|
||||
size_t res = 0;
|
||||
if (!dh)
|
||||
return 0;
|
||||
|
||||
while (!res)
|
||||
{
|
||||
MP_t q1 = NULL;
|
||||
|
||||
if (!MDH_generate_key(dh))
|
||||
return 0;
|
||||
|
||||
MP_gethex(q1, Q1024, res);
|
||||
assert(res);
|
||||
|
||||
res = isValidPublicKey(dh->pub_key, dh->p, q1);
|
||||
if (!res)
|
||||
{
|
||||
MP_free(dh->pub_key);
|
||||
MP_free(dh->priv_key);
|
||||
dh->pub_key = dh->priv_key = 0;
|
||||
}
|
||||
|
||||
MP_free(q1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* fill pubkey with the public key in BIG ENDIAN order
|
||||
* 00 00 00 00 00 x1 x2 x3 .....
|
||||
*/
|
||||
|
||||
static int
|
||||
DHGetPublicKey(MDH *dh, uint8_t *pubkey, size_t nPubkeyLen)
|
||||
{
|
||||
int len;
|
||||
if (!dh || !dh->pub_key)
|
||||
return 0;
|
||||
|
||||
len = MP_bytes(dh->pub_key);
|
||||
if (len <= 0 || len > (int) nPubkeyLen)
|
||||
return 0;
|
||||
|
||||
memset(pubkey, 0, nPubkeyLen);
|
||||
MP_setbin(dh->pub_key, pubkey + (nPubkeyLen - len), len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0 /* unused */
|
||||
static int
|
||||
DHGetPrivateKey(MDH *dh, uint8_t *privkey, size_t nPrivkeyLen)
|
||||
{
|
||||
if (!dh || !dh->priv_key)
|
||||
return 0;
|
||||
|
||||
int len = MP_bytes(dh->priv_key);
|
||||
if (len <= 0 || len > (int) nPrivkeyLen)
|
||||
return 0;
|
||||
|
||||
memset(privkey, 0, nPrivkeyLen);
|
||||
MP_setbin(dh->priv_key, privkey + (nPrivkeyLen - len), len);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* computes the shared secret key from the private MDH value and the
|
||||
* other party's public key (pubkey)
|
||||
*/
|
||||
static int
|
||||
DHComputeSharedSecretKey(MDH *dh, uint8_t *pubkey, size_t nPubkeyLen,
|
||||
uint8_t *secret)
|
||||
{
|
||||
MP_t q1 = NULL, pubkeyBn = NULL;
|
||||
size_t len;
|
||||
int res;
|
||||
|
||||
if (!dh || !secret || nPubkeyLen >= INT_MAX)
|
||||
return -1;
|
||||
|
||||
MP_getbin(pubkeyBn, pubkey, nPubkeyLen);
|
||||
if (!pubkeyBn)
|
||||
return -1;
|
||||
|
||||
MP_gethex(q1, Q1024, len);
|
||||
assert(len);
|
||||
|
||||
if (isValidPublicKey(pubkeyBn, dh->p, q1))
|
||||
res = MDH_compute_key(secret, nPubkeyLen, pubkeyBn, dh);
|
||||
else
|
||||
res = -1;
|
||||
|
||||
MP_free(q1);
|
||||
MP_free(pubkeyBn);
|
||||
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/* librtmp - Diffie-Hellmann Key Exchange
|
||||
* Copyright (C) 2009 Andrej Stepanchuk
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
/* from RFC 3526, see http://www.ietf.org/rfc/rfc3526.txt */
|
||||
|
||||
/* 2^768 - 2 ^704 - 1 + 2^64 * { [2^638 pi] + 149686 } */
|
||||
#define P768 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 } */
|
||||
#define P1024 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \
|
||||
"FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* Group morder largest prime factor: */
|
||||
#define Q1024 \
|
||||
"7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \
|
||||
"948127044533E63A0105DF531D89CD9128A5043CC71A026E" \
|
||||
"F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \
|
||||
"F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \
|
||||
"F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \
|
||||
"FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 } */
|
||||
#define P1536 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 } */
|
||||
#define P2048 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
|
||||
"15728E5A8AACAA68FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 } */
|
||||
#define P3072 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
|
||||
"43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 } */
|
||||
#define P4096 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
|
||||
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
|
||||
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
|
||||
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
|
||||
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
|
||||
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
|
||||
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" \
|
||||
"FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 } */
|
||||
#define P6144 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
|
||||
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
|
||||
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
|
||||
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
|
||||
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
|
||||
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
|
||||
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
|
||||
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
|
||||
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
|
||||
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
|
||||
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
|
||||
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
|
||||
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
|
||||
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
|
||||
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
|
||||
"12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF"
|
||||
|
||||
/* 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 } */
|
||||
#define P8192 \
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
|
||||
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
|
||||
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
|
||||
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
|
||||
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
|
||||
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
|
||||
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
|
||||
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
|
||||
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
|
||||
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
|
||||
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
|
||||
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
|
||||
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
|
||||
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
|
||||
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
|
||||
"12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" \
|
||||
"38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" \
|
||||
"741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" \
|
||||
"3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" \
|
||||
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" \
|
||||
"4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" \
|
||||
"062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" \
|
||||
"4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" \
|
||||
"B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" \
|
||||
"4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" \
|
||||
"9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" \
|
||||
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF"
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,665 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "rtmp_sys.h"
|
||||
#include "log.h"
|
||||
#include "http.h"
|
||||
|
||||
#ifdef CRYPTO
|
||||
#ifdef USE_POLARSSL
|
||||
#include <polarssl/sha2.h>
|
||||
#ifndef SHA256_DIGEST_LENGTH
|
||||
#define SHA256_DIGEST_LENGTH 32
|
||||
#endif
|
||||
#define HMAC_CTX sha2_context
|
||||
#define HMAC_setup(ctx, key, len) sha2_hmac_starts(&ctx, (unsigned char *)key, len, 0)
|
||||
#define HMAC_crunch(ctx, buf, len) sha2_hmac_update(&ctx, buf, len)
|
||||
#define HMAC_finish(ctx, dig, dlen) dlen = SHA256_DIGEST_LENGTH; sha2_hmac_finish(&ctx, dig)
|
||||
#define HMAC_close(ctx)
|
||||
#elif defined(USE_GNUTLS)
|
||||
#include <nettle/hmac.h>
|
||||
#ifndef SHA256_DIGEST_LENGTH
|
||||
#define SHA256_DIGEST_LENGTH 32
|
||||
#endif
|
||||
#undef HMAC_CTX
|
||||
#define HMAC_CTX struct hmac_sha256_ctx
|
||||
#define HMAC_setup(ctx, key, len) hmac_sha256_set_key(&ctx, len, key)
|
||||
#define HMAC_crunch(ctx, buf, len) hmac_sha256_update(&ctx, len, buf)
|
||||
#define HMAC_finish(ctx, dig, dlen) dlen = SHA256_DIGEST_LENGTH; hmac_sha256_digest(&ctx, SHA256_DIGEST_LENGTH, dig)
|
||||
#define HMAC_close(ctx)
|
||||
#else /* USE_OPENSSL */
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rc4.h>
|
||||
#define HMAC_setup(ctx, key, len) HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, (unsigned char *)key, len, EVP_sha256(), 0)
|
||||
#define HMAC_crunch(ctx, buf, len) HMAC_Update(&ctx, (unsigned char *)buf, len)
|
||||
#define HMAC_finish(ctx, dig, dlen) HMAC_Final(&ctx, (unsigned char *)dig, &dlen);
|
||||
#define HMAC_close(ctx) HMAC_CTX_cleanup(&ctx)
|
||||
#endif
|
||||
|
||||
extern void RTMP_TLS_Init();
|
||||
extern TLS_CTX RTMP_TLS_ctx;
|
||||
|
||||
#include <zlib.h>
|
||||
#include "strncasecmp.h"
|
||||
|
||||
#endif /* CRYPTO */
|
||||
|
||||
#define AGENT "Mozilla/5.0"
|
||||
|
||||
HTTPResult
|
||||
HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb)
|
||||
{
|
||||
char *host, *path;
|
||||
char *p1, *p2;
|
||||
char hbuf[256];
|
||||
int port = 80;
|
||||
#ifdef CRYPTO
|
||||
int ssl = 0;
|
||||
#endif
|
||||
int hlen, flen = 0;
|
||||
int rc, i;
|
||||
int len_known;
|
||||
HTTPResult ret = HTTPRES_OK;
|
||||
struct sockaddr_in sa;
|
||||
RTMPSockBuf sb = {0};
|
||||
|
||||
http->status = -1;
|
||||
|
||||
memset(&sa, 0, sizeof(struct sockaddr_in));
|
||||
sa.sin_family = AF_INET;
|
||||
|
||||
/* we only handle http here */
|
||||
if (strncasecmp(url, "http", 4))
|
||||
return HTTPRES_BAD_REQUEST;
|
||||
|
||||
if (url[4] == 's')
|
||||
{
|
||||
#ifdef CRYPTO
|
||||
ssl = 1;
|
||||
port = 443;
|
||||
if (!RTMP_TLS_ctx)
|
||||
RTMP_TLS_Init();
|
||||
#else
|
||||
return HTTPRES_BAD_REQUEST;
|
||||
#endif
|
||||
}
|
||||
|
||||
p1 = strchr(url + 4, ':');
|
||||
if (!p1 || strncmp(p1, "://", 3))
|
||||
return HTTPRES_BAD_REQUEST;
|
||||
|
||||
host = p1 + 3;
|
||||
path = strchr(host, '/');
|
||||
hlen = path - host;
|
||||
strncpy(hbuf, host, hlen);
|
||||
hbuf[hlen] = '\0';
|
||||
host = hbuf;
|
||||
p1 = strrchr(host, ':');
|
||||
if (p1)
|
||||
{
|
||||
*p1++ = '\0';
|
||||
port = atoi(p1);
|
||||
}
|
||||
|
||||
sa.sin_addr.s_addr = inet_addr(host);
|
||||
if (sa.sin_addr.s_addr == INADDR_NONE)
|
||||
{
|
||||
struct hostent *hp = gethostbyname(host);
|
||||
if (!hp || !hp->h_addr)
|
||||
return HTTPRES_LOST_CONNECTION;
|
||||
sa.sin_addr = *(struct in_addr *)hp->h_addr;
|
||||
}
|
||||
sa.sin_port = htons(port);
|
||||
sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sb.sb_socket == -1)
|
||||
return HTTPRES_LOST_CONNECTION;
|
||||
i =
|
||||
sprintf(sb.sb_buf,
|
||||
"GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\nReferer: %.*s\r\n",
|
||||
path, AGENT, host, (int)(path - url + 1), url);
|
||||
if (http->date[0])
|
||||
i += sprintf(sb.sb_buf + i, "If-Modified-Since: %s\r\n", http->date);
|
||||
i += sprintf(sb.sb_buf + i, "\r\n");
|
||||
|
||||
if (connect
|
||||
(sb.sb_socket, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0)
|
||||
{
|
||||
ret = HTTPRES_LOST_CONNECTION;
|
||||
goto leave;
|
||||
}
|
||||
#ifdef CRYPTO
|
||||
if (ssl)
|
||||
{
|
||||
#ifdef NO_SSL
|
||||
RTMP_Log(RTMP_LOGERROR, "%s, No SSL/TLS support", __FUNCTION__);
|
||||
ret = HTTPRES_BAD_REQUEST;
|
||||
goto leave;
|
||||
#else
|
||||
TLS_client(RTMP_TLS_ctx, sb.sb_ssl);
|
||||
TLS_setfd(sb.sb_ssl, sb.sb_socket);
|
||||
if (TLS_connect(sb.sb_ssl) < 0)
|
||||
{
|
||||
RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
|
||||
ret = HTTPRES_LOST_CONNECTION;
|
||||
goto leave;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
RTMPSockBuf_Send(&sb, sb.sb_buf, i);
|
||||
|
||||
/* set timeout */
|
||||
#define HTTP_TIMEOUT 5
|
||||
{
|
||||
SET_RCVTIMEO(tv, HTTP_TIMEOUT);
|
||||
if (setsockopt
|
||||
(sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
|
||||
{
|
||||
RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
|
||||
__FUNCTION__, HTTP_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
sb.sb_size = 0;
|
||||
sb.sb_timedout = FALSE;
|
||||
if (RTMPSockBuf_Fill(&sb) < 1)
|
||||
{
|
||||
ret = HTTPRES_LOST_CONNECTION;
|
||||
goto leave;
|
||||
}
|
||||
if (strncmp(sb.sb_buf, "HTTP/1", 6))
|
||||
{
|
||||
ret = HTTPRES_BAD_REQUEST;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
p1 = strchr(sb.sb_buf, ' ');
|
||||
rc = atoi(p1 + 1);
|
||||
http->status = rc;
|
||||
|
||||
if (rc >= 300)
|
||||
{
|
||||
if (rc == 304)
|
||||
{
|
||||
ret = HTTPRES_OK_NOT_MODIFIED;
|
||||
goto leave;
|
||||
}
|
||||
else if (rc == 404)
|
||||
ret = HTTPRES_NOT_FOUND;
|
||||
else if (rc >= 500)
|
||||
ret = HTTPRES_SERVER_ERROR;
|
||||
else if (rc >= 400)
|
||||
ret = HTTPRES_BAD_REQUEST;
|
||||
else
|
||||
ret = HTTPRES_REDIRECTED;
|
||||
}
|
||||
|
||||
p1 = memchr(sb.sb_buf, '\n', sb.sb_size);
|
||||
if (!p1)
|
||||
{
|
||||
ret = HTTPRES_BAD_REQUEST;
|
||||
goto leave;
|
||||
}
|
||||
sb.sb_start = p1 + 1;
|
||||
sb.sb_size -= sb.sb_start - sb.sb_buf;
|
||||
|
||||
while ((p2 = memchr(sb.sb_start, '\r', sb.sb_size)))
|
||||
{
|
||||
if (*sb.sb_start == '\r')
|
||||
{
|
||||
sb.sb_start += 2;
|
||||
sb.sb_size -= 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
if (!strncasecmp
|
||||
(sb.sb_start, "Content-Length: ", sizeof("Content-Length: ") - 1))
|
||||
{
|
||||
flen = atoi(sb.sb_start + sizeof("Content-Length: ") - 1);
|
||||
}
|
||||
else
|
||||
if (!strncasecmp
|
||||
(sb.sb_start, "Last-Modified: ", sizeof("Last-Modified: ") - 1))
|
||||
{
|
||||
*p2 = '\0';
|
||||
strcpy(http->date, sb.sb_start + sizeof("Last-Modified: ") - 1);
|
||||
}
|
||||
p2 += 2;
|
||||
sb.sb_size -= p2 - sb.sb_start;
|
||||
sb.sb_start = p2;
|
||||
if (sb.sb_size < 1)
|
||||
{
|
||||
if (RTMPSockBuf_Fill(&sb) < 1)
|
||||
{
|
||||
ret = HTTPRES_LOST_CONNECTION;
|
||||
goto leave;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
len_known = flen > 0;
|
||||
while ((!len_known || flen > 0) &&
|
||||
(sb.sb_size > 0 || RTMPSockBuf_Fill(&sb) > 0))
|
||||
{
|
||||
cb(sb.sb_start, 1, sb.sb_size, http->data);
|
||||
if (len_known)
|
||||
flen -= sb.sb_size;
|
||||
http->size += sb.sb_size;
|
||||
sb.sb_size = 0;
|
||||
}
|
||||
|
||||
if (flen > 0)
|
||||
ret = HTTPRES_LOST_CONNECTION;
|
||||
|
||||
leave:
|
||||
RTMPSockBuf_Close(&sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CRYPTO
|
||||
|
||||
#define CHUNK 16384
|
||||
|
||||
struct info
|
||||
{
|
||||
z_stream *zs;
|
||||
HMAC_CTX ctx;
|
||||
int first;
|
||||
int zlib;
|
||||
int size;
|
||||
};
|
||||
|
||||
static size_t
|
||||
swfcrunch(void *ptr, size_t size, size_t nmemb, void *stream)
|
||||
{
|
||||
struct info *i = stream;
|
||||
char *p = ptr;
|
||||
size_t len = size * nmemb;
|
||||
|
||||
if (i->first)
|
||||
{
|
||||
i->first = 0;
|
||||
/* compressed? */
|
||||
if (!strncmp(p, "CWS", 3))
|
||||
{
|
||||
*p = 'F';
|
||||
i->zlib = 1;
|
||||
}
|
||||
HMAC_crunch(i->ctx, (unsigned char *)p, 8);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
i->size = 8;
|
||||
}
|
||||
|
||||
if (i->zlib)
|
||||
{
|
||||
unsigned char out[CHUNK];
|
||||
i->zs->next_in = (unsigned char *)p;
|
||||
i->zs->avail_in = len;
|
||||
do
|
||||
{
|
||||
i->zs->avail_out = CHUNK;
|
||||
i->zs->next_out = out;
|
||||
inflate(i->zs, Z_NO_FLUSH);
|
||||
len = CHUNK - i->zs->avail_out;
|
||||
i->size += len;
|
||||
HMAC_crunch(i->ctx, out, len);
|
||||
}
|
||||
while (i->zs->avail_out == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
i->size += len;
|
||||
HMAC_crunch(i->ctx, (unsigned char *)p, len);
|
||||
}
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static int tzoff;
|
||||
static int tzchecked;
|
||||
|
||||
#define JAN02_1980 318340800
|
||||
|
||||
static const char *monthtab[12] = { "Jan", "Feb", "Mar",
|
||||
"Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep",
|
||||
"Oct", "Nov", "Dec"
|
||||
};
|
||||
static const char *days[] =
|
||||
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
|
||||
/* Parse an HTTP datestamp into Unix time */
|
||||
static time_t
|
||||
make_unix_time(char *s)
|
||||
{
|
||||
struct tm time;
|
||||
int i, ysub = 1900, fmt = 0;
|
||||
char *month;
|
||||
char *n;
|
||||
time_t res;
|
||||
|
||||
if (s[3] != ' ')
|
||||
{
|
||||
fmt = 1;
|
||||
if (s[3] != ',')
|
||||
ysub = 0;
|
||||
}
|
||||
for (n = s; *n; ++n)
|
||||
if (*n == '-' || *n == ':')
|
||||
*n = ' ';
|
||||
|
||||
time.tm_mon = 0;
|
||||
n = strchr(s, ' ');
|
||||
if (fmt)
|
||||
{
|
||||
/* Day, DD-MMM-YYYY HH:MM:SS GMT */
|
||||
time.tm_mday = strtol(n + 1, &n, 0);
|
||||
month = n + 1;
|
||||
n = strchr(month, ' ');
|
||||
time.tm_year = strtol(n + 1, &n, 0);
|
||||
time.tm_hour = strtol(n + 1, &n, 0);
|
||||
time.tm_min = strtol(n + 1, &n, 0);
|
||||
time.tm_sec = strtol(n + 1, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Unix ctime() format. Does not conform to HTTP spec. */
|
||||
/* Day MMM DD HH:MM:SS YYYY */
|
||||
month = n + 1;
|
||||
n = strchr(month, ' ');
|
||||
while (isspace(*n))
|
||||
n++;
|
||||
time.tm_mday = strtol(n, &n, 0);
|
||||
time.tm_hour = strtol(n + 1, &n, 0);
|
||||
time.tm_min = strtol(n + 1, &n, 0);
|
||||
time.tm_sec = strtol(n + 1, &n, 0);
|
||||
time.tm_year = strtol(n + 1, NULL, 0);
|
||||
}
|
||||
if (time.tm_year > 100)
|
||||
time.tm_year -= ysub;
|
||||
|
||||
for (i = 0; i < 12; i++)
|
||||
if (!strncasecmp(month, monthtab[i], 3))
|
||||
{
|
||||
time.tm_mon = i;
|
||||
break;
|
||||
}
|
||||
time.tm_isdst = 0; /* daylight saving is never in effect in GMT */
|
||||
|
||||
/* this is normally the value of extern int timezone, but some
|
||||
* braindead C libraries don't provide it.
|
||||
*/
|
||||
if (!tzchecked)
|
||||
{
|
||||
struct tm *tc;
|
||||
time_t then = JAN02_1980;
|
||||
tc = localtime(&then);
|
||||
tzoff = (12 - tc->tm_hour) * 3600 + tc->tm_min * 60 + tc->tm_sec;
|
||||
tzchecked = 1;
|
||||
}
|
||||
res = mktime(&time);
|
||||
/* Unfortunately, mktime() assumes the input is in local time,
|
||||
* not GMT, so we have to correct it here.
|
||||
*/
|
||||
if (res != -1)
|
||||
res += tzoff;
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Convert a Unix time to a network time string
|
||||
* Weekday, DD-MMM-YYYY HH:MM:SS GMT
|
||||
*/
|
||||
static void
|
||||
strtime(time_t * t, char *s)
|
||||
{
|
||||
struct tm *tm;
|
||||
|
||||
tm = gmtime((time_t *) t);
|
||||
sprintf(s, "%s, %02d %s %d %02d:%02d:%02d GMT",
|
||||
days[tm->tm_wday], tm->tm_mday, monthtab[tm->tm_mon],
|
||||
tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
}
|
||||
|
||||
#define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
|
||||
|
||||
int
|
||||
RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
|
||||
int age)
|
||||
{
|
||||
FILE *f = NULL;
|
||||
char *path, date[64], cctim[64];
|
||||
long pos = 0;
|
||||
time_t ctim = -1, cnow;
|
||||
int i, got = 0, ret = 0;
|
||||
unsigned int hlen;
|
||||
struct info in = { 0 };
|
||||
struct HTTP_ctx http = { 0 };
|
||||
HTTPResult httpres;
|
||||
z_stream zs = { 0 };
|
||||
AVal home, hpre;
|
||||
|
||||
date[0] = '\0';
|
||||
#ifdef _WIN32
|
||||
#ifdef XBMC4XBOX
|
||||
hpre.av_val = "Q:";
|
||||
hpre.av_len = 2;
|
||||
home.av_val = "\\UserData";
|
||||
#else
|
||||
hpre.av_val = getenv("HOMEDRIVE");
|
||||
hpre.av_len = strlen(hpre.av_val);
|
||||
home.av_val = getenv("HOMEPATH");
|
||||
#endif
|
||||
#define DIRSEP "\\"
|
||||
|
||||
#else /* !_WIN32 */
|
||||
hpre.av_val = "";
|
||||
hpre.av_len = 0;
|
||||
home.av_val = getenv("HOME");
|
||||
#define DIRSEP "/"
|
||||
#endif
|
||||
if (!home.av_val)
|
||||
home.av_val = ".";
|
||||
home.av_len = strlen(home.av_val);
|
||||
|
||||
/* SWF hash info is cached in a fixed-format file.
|
||||
* url: <url of SWF file>
|
||||
* ctim: HTTP datestamp of when we last checked it.
|
||||
* date: HTTP datestamp of the SWF's last modification.
|
||||
* size: SWF size in hex
|
||||
* hash: SWF hash in hex
|
||||
*
|
||||
* These fields must be present in this order. All fields
|
||||
* besides URL are fixed size.
|
||||
*/
|
||||
path = malloc(hpre.av_len + home.av_len + sizeof(DIRSEP ".swfinfo"));
|
||||
sprintf(path, "%s%s" DIRSEP ".swfinfo", hpre.av_val, home.av_val);
|
||||
|
||||
f = fopen(path, "r+");
|
||||
while (f)
|
||||
{
|
||||
char buf[4096], *file, *p;
|
||||
|
||||
file = strchr(url, '/');
|
||||
if (!file)
|
||||
break;
|
||||
file += 2;
|
||||
file = strchr(file, '/');
|
||||
if (!file)
|
||||
break;
|
||||
file++;
|
||||
hlen = file - url;
|
||||
p = strrchr(file, '/');
|
||||
if (p)
|
||||
file = p;
|
||||
else
|
||||
file--;
|
||||
|
||||
while (fgets(buf, sizeof(buf), f))
|
||||
{
|
||||
char *r1;
|
||||
|
||||
got = 0;
|
||||
|
||||
if (strncmp(buf, "url: ", 5))
|
||||
continue;
|
||||
if (strncmp(buf + 5, url, hlen))
|
||||
continue;
|
||||
r1 = strrchr(buf, '/');
|
||||
i = strlen(r1);
|
||||
r1[--i] = '\0';
|
||||
if (strncmp(r1, file, i))
|
||||
continue;
|
||||
pos = ftell(f);
|
||||
while (got < 4 && fgets(buf, sizeof(buf), f))
|
||||
{
|
||||
if (!strncmp(buf, "size: ", 6))
|
||||
{
|
||||
*size = strtol(buf + 6, NULL, 16);
|
||||
got++;
|
||||
}
|
||||
else if (!strncmp(buf, "hash: ", 6))
|
||||
{
|
||||
unsigned char *ptr = hash, *in = (unsigned char *)buf + 6;
|
||||
int l = strlen((char *)in) - 1;
|
||||
for (i = 0; i < l; i += 2)
|
||||
*ptr++ = (HEX2BIN(in[i]) << 4) | HEX2BIN(in[i + 1]);
|
||||
got++;
|
||||
}
|
||||
else if (!strncmp(buf, "date: ", 6))
|
||||
{
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
strncpy(date, buf + 6, sizeof(date));
|
||||
got++;
|
||||
}
|
||||
else if (!strncmp(buf, "ctim: ", 6))
|
||||
{
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
ctim = make_unix_time(buf + 6);
|
||||
got++;
|
||||
}
|
||||
else if (!strncmp(buf, "url: ", 5))
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
cnow = time(NULL);
|
||||
/* If we got a cache time, see if it's young enough to use directly */
|
||||
if (age && ctim > 0)
|
||||
{
|
||||
ctim = cnow - ctim;
|
||||
ctim /= 3600 * 24; /* seconds to days */
|
||||
if (ctim < age) /* ok, it's new enough */
|
||||
goto out;
|
||||
}
|
||||
|
||||
in.first = 1;
|
||||
HMAC_setup(in.ctx, "Genuine Adobe Flash Player 001", 30);
|
||||
inflateInit(&zs);
|
||||
in.zs = &zs;
|
||||
|
||||
http.date = date;
|
||||
http.data = ∈
|
||||
|
||||
httpres = HTTP_get(&http, url, swfcrunch);
|
||||
|
||||
inflateEnd(&zs);
|
||||
|
||||
if (httpres != HTTPRES_OK && httpres != HTTPRES_OK_NOT_MODIFIED)
|
||||
{
|
||||
ret = -1;
|
||||
if (httpres == HTTPRES_LOST_CONNECTION)
|
||||
RTMP_Log(RTMP_LOGERROR, "%s: connection lost while downloading swfurl %s",
|
||||
__FUNCTION__, url);
|
||||
else if (httpres == HTTPRES_NOT_FOUND)
|
||||
RTMP_Log(RTMP_LOGERROR, "%s: swfurl %s not found", __FUNCTION__, url);
|
||||
else
|
||||
RTMP_Log(RTMP_LOGERROR, "%s: couldn't contact swfurl %s (HTTP error %d)",
|
||||
__FUNCTION__, url, http.status);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (got && pos)
|
||||
fseek(f, pos, SEEK_SET);
|
||||
else
|
||||
{
|
||||
char *q;
|
||||
if (!f)
|
||||
f = fopen(path, "w");
|
||||
if (!f)
|
||||
{
|
||||
int err = errno;
|
||||
RTMP_Log(RTMP_LOGERROR,
|
||||
"%s: couldn't open %s for writing, errno %d (%s)",
|
||||
__FUNCTION__, path, err, strerror(err));
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
q = strchr(url, '?');
|
||||
if (q)
|
||||
i = q - url;
|
||||
else
|
||||
i = strlen(url);
|
||||
|
||||
fprintf(f, "url: %.*s\n", i, url);
|
||||
}
|
||||
strtime(&cnow, cctim);
|
||||
fprintf(f, "ctim: %s\n", cctim);
|
||||
|
||||
if (!in.first)
|
||||
{
|
||||
HMAC_finish(in.ctx, hash, hlen);
|
||||
*size = in.size;
|
||||
|
||||
fprintf(f, "date: %s\n", date);
|
||||
fprintf(f, "size: %08x\n", in.size);
|
||||
fprintf(f, "hash: ");
|
||||
for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
|
||||
fprintf(f, "%02x", hash[i]);
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
}
|
||||
HMAC_close(in.ctx);
|
||||
out:
|
||||
free(path);
|
||||
if (f)
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
int
|
||||
RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
|
||||
int age)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef __RTMP_HTTP_H__
|
||||
#define __RTMP_HTTP_H__
|
||||
/*
|
||||
* Copyright (C) 2010 Howard Chu
|
||||
* Copyright (C) 2010 Antti Ajanki
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
HTTPRES_OK, /* result OK */
|
||||
HTTPRES_OK_NOT_MODIFIED, /* not modified since last request */
|
||||
HTTPRES_NOT_FOUND, /* not found */
|
||||
HTTPRES_BAD_REQUEST, /* client error */
|
||||
HTTPRES_SERVER_ERROR, /* server reported an error */
|
||||
HTTPRES_REDIRECTED, /* resource has been moved */
|
||||
HTTPRES_LOST_CONNECTION /* connection lost while waiting for data */
|
||||
} HTTPResult;
|
||||
|
||||
struct HTTP_ctx {
|
||||
char *date;
|
||||
int size;
|
||||
int status;
|
||||
void *data;
|
||||
};
|
||||
|
||||
typedef size_t (HTTP_read_callback)(void *ptr, size_t size, size_t nmemb, void *stream);
|
||||
|
||||
HTTPResult HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,210 @@
|
|||
.TH LIBRTMP 3 "2011-07-20" "RTMPDump v2.4"
|
||||
.\" Copyright 2011 Howard Chu.
|
||||
.\" Copying permitted according to the GNU General Public License V2.
|
||||
.SH NAME
|
||||
librtmp \- RTMPDump Real-Time Messaging Protocol API
|
||||
.SH LIBRARY
|
||||
RTMPDump RTMP (librtmp, -lrtmp)
|
||||
.SH SYNOPSIS
|
||||
.B #include <librtmp/rtmp.h>
|
||||
.SH DESCRIPTION
|
||||
The Real-Time Messaging Protocol (RTMP) is used for streaming
|
||||
multimedia content across a TCP/IP network. This API provides most client
|
||||
functions and a few server functions needed to support RTMP, RTMP tunneled
|
||||
in HTTP (RTMPT), encrypted RTMP (RTMPE), RTMP over SSL/TLS (RTMPS) and
|
||||
tunneled variants of these encrypted types (RTMPTE, RTMPTS). The basic
|
||||
RTMP specification has been published by Adobe but this API was
|
||||
reverse-engineered without use of the Adobe specification. As such, it may
|
||||
deviate from any published specifications but it usually duplicates the
|
||||
actual behavior of the original Adobe clients.
|
||||
|
||||
The RTMPDump software package includes a basic client utility program
|
||||
in
|
||||
.BR rtmpdump (1),
|
||||
some sample servers, and a library used to provide programmatic access
|
||||
to the RTMP protocol. This man page gives an overview of the RTMP
|
||||
library routines. These routines are found in the -lrtmp library. Many
|
||||
other routines are also available, but they are not documented yet.
|
||||
|
||||
The basic interaction is as follows. A session handle is created using
|
||||
.BR RTMP_Alloc ()
|
||||
and initialized using
|
||||
.BR RTMP_Init ().
|
||||
All session parameters are provided using
|
||||
.BR RTMP_SetupURL ().
|
||||
The network connection is established using
|
||||
.BR RTMP_Connect (),
|
||||
and then the RTMP session is established using
|
||||
.BR RTMP_ConnectStream ().
|
||||
The stream is read using
|
||||
.BR RTMP_Read ().
|
||||
A client can publish a stream by calling
|
||||
.BR RTMP_EnableWrite ()
|
||||
before the
|
||||
.BR RTMP_Connect ()
|
||||
call, and then using
|
||||
.BR RTMP_Write ()
|
||||
after the session is established.
|
||||
While a stream is playing it may be paused and unpaused using
|
||||
.BR RTMP_Pause ().
|
||||
The stream playback position can be moved using
|
||||
.BR RTMP_Seek ().
|
||||
When
|
||||
.BR RTMP_Read ()
|
||||
returns 0 bytes, the stream is complete and may be closed using
|
||||
.BR RTMP_Close ().
|
||||
The session handle is freed using
|
||||
.BR RTMP_Free ().
|
||||
|
||||
All data is transferred using FLV format. The basic session requires
|
||||
an RTMP URL. The RTMP URL format is of the form
|
||||
.nf
|
||||
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
|
||||
.fi
|
||||
|
||||
Plain rtmp, as well as tunneled and encrypted sessions are supported.
|
||||
|
||||
Additional options may be specified by appending space-separated
|
||||
key=value pairs to the URL. Special characters in values may need
|
||||
to be escaped to prevent misinterpretation by the option parser.
|
||||
The escape encoding uses a backslash followed by two hexadecimal digits
|
||||
representing the ASCII value of the character. E.g., spaces must
|
||||
be escaped as \fB\\20\fP and backslashes must be escaped as \fB\\5c\fP.
|
||||
.SH OPTIONS
|
||||
.SS "Network Parameters"
|
||||
These options define how to connect to the media server.
|
||||
.TP
|
||||
.BI socks= host:port
|
||||
Use the specified SOCKS4 proxy.
|
||||
.SS "Connection Parameters"
|
||||
These options define the content of the RTMP Connect request packet.
|
||||
If correct values are not provided, the media server will reject the
|
||||
connection attempt.
|
||||
.TP
|
||||
.BI app= name
|
||||
Name of application to connect to on the RTMP server. Overrides
|
||||
the app in the RTMP URL. Sometimes the librtmp URL parser cannot
|
||||
determine the app name automatically, so it must be given explicitly
|
||||
using this option.
|
||||
.TP
|
||||
.BI tcUrl= url
|
||||
URL of the target stream. Defaults to rtmp[t][e|s]://host[:port]/app.
|
||||
.TP
|
||||
.BI pageUrl= url
|
||||
URL of the web page in which the media was embedded. By default no
|
||||
value will be sent.
|
||||
.TP
|
||||
.BI swfUrl= url
|
||||
URL of the SWF player for the media. By default no value will be sent.
|
||||
.TP
|
||||
.BI flashVer= version
|
||||
Version of the Flash plugin used to run the SWF player. The
|
||||
default is "LNX 10,0,32,18".
|
||||
.TP
|
||||
.BI conn= type:data
|
||||
Append arbitrary AMF data to the Connect message. The type
|
||||
must be B for Boolean, N for number, S for string, O for object, or Z
|
||||
for null. For Booleans the data must be either 0 or 1 for FALSE or TRUE,
|
||||
respectively. Likewise for Objects the data must be 0 or 1 to end or
|
||||
begin an object, respectively. Data items in subobjects may be named, by
|
||||
prefixing the type with 'N' and specifying the name before the value, e.g.
|
||||
NB:myFlag:1. This option may be used multiple times to construct arbitrary
|
||||
AMF sequences. E.g.
|
||||
.nf
|
||||
conn=B:1 conn=S:authMe conn=O:1 conn=NN:code:1.23 conn=NS:flag:ok conn=O:0
|
||||
.fi
|
||||
.SS "Session Parameters"
|
||||
These options take effect after the Connect request has succeeded.
|
||||
.TP
|
||||
.BI playpath= path
|
||||
Overrides the playpath parsed from the RTMP URL. Sometimes the
|
||||
rtmpdump URL parser cannot determine the correct playpath
|
||||
automatically, so it must be given explicitly using this option.
|
||||
.TP
|
||||
.BI playlist= 0|1
|
||||
If the value is 1 or TRUE, issue a set_playlist command before sending the
|
||||
play command. The playlist will just contain the current playpath. If the
|
||||
value is 0 or FALSE, the set_playlist command will not be sent. The
|
||||
default is FALSE.
|
||||
.TP
|
||||
.BI live= 0|1
|
||||
Specify that the media is a live stream. No resuming or seeking in
|
||||
live streams is possible.
|
||||
.TP
|
||||
.BI subscribe= path
|
||||
Name of live stream to subscribe to. Defaults to
|
||||
.IR playpath .
|
||||
.TP
|
||||
.BI start= num
|
||||
Start at
|
||||
.I num
|
||||
seconds into the stream. Not valid for live streams.
|
||||
.TP
|
||||
.BI stop= num
|
||||
Stop at
|
||||
.I num
|
||||
seconds into the stream.
|
||||
.TP
|
||||
.BI buffer= num
|
||||
Set buffer time to
|
||||
.I num
|
||||
milliseconds. The default is 30000.
|
||||
.TP
|
||||
.BI timeout= num
|
||||
Timeout the session after
|
||||
.I num
|
||||
seconds without receiving any data from the server. The default is 120.
|
||||
.SS "Security Parameters"
|
||||
These options handle additional authentication requests from the server.
|
||||
.TP
|
||||
.BI token= key
|
||||
Key for SecureToken response, used if the server requires SecureToken
|
||||
authentication.
|
||||
.TP
|
||||
.BI jtv= JSON
|
||||
JSON token used by legacy Justin.tv servers. Invokes NetStream.Authenticate.UsherToken
|
||||
.TP
|
||||
.BI swfVfy= 0|1
|
||||
If the value is 1 or TRUE, the SWF player is retrieved from the
|
||||
specified
|
||||
.I swfUrl
|
||||
for performing SWF Verification. The SWF hash and size (used in the
|
||||
verification step) are computed automatically. Also the SWF information is
|
||||
cached in a
|
||||
.I .swfinfo
|
||||
file in the user's home directory, so that it doesn't need to be retrieved
|
||||
and recalculated every time. The .swfinfo file records
|
||||
the SWF URL, the time it was fetched, the modification timestamp of the SWF
|
||||
file, its size, and its hash. By default, the cached info will be used
|
||||
for 30 days before re-checking.
|
||||
.TP
|
||||
.BI swfAge= days
|
||||
Specify how many days to use the cached SWF info before re-checking. Use
|
||||
0 to always check the SWF URL. Note that if the check shows that the
|
||||
SWF file has the same modification timestamp as before, it will not be
|
||||
retrieved again.
|
||||
.SH EXAMPLES
|
||||
An example character string suitable for use with
|
||||
.BR RTMP_SetupURL ():
|
||||
.nf
|
||||
"rtmp://flashserver:1935/ondemand/thefile swfUrl=http://flashserver/player.swf swfVfy=1"
|
||||
.fi
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B HOME
|
||||
The value of
|
||||
.RB $ HOME
|
||||
is used as the location for the
|
||||
.I .swfinfo
|
||||
file.
|
||||
.SH FILES
|
||||
.TP
|
||||
.I $HOME/.swfinfo
|
||||
Cache of SWF Verification information
|
||||
.SH "SEE ALSO"
|
||||
.BR rtmpdump (1),
|
||||
.BR rtmpgw (8)
|
||||
.SH AUTHORS
|
||||
Andrej Stepanchuk, Howard Chu, The Flvstreamer Team
|
||||
.br
|
||||
<http://rtmpdump.mplayerhq.hu>
|
|
@ -0,0 +1,312 @@
|
|||
<HTML>
|
||||
<HEAD>
|
||||
<title>LIBRTMP(3): </title></head>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><td>LIBRTMP(3)<td align="center"><td align="right">LIBRTMP(3)
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr><td>RTMPDump v2.4<td align="center">2011-07-20<td align="right">LIBRTMP(3)
|
||||
</tfoot>
|
||||
<tbody><tr><td colspan="3"><br><br><ul>
|
||||
<!-- Copyright 2011 Howard Chu.
|
||||
Copying permitted according to the GNU General Public License V2.-->
|
||||
</ul>
|
||||
|
||||
<h3>NAME</h3><ul>
|
||||
librtmp − RTMPDump Real-Time Messaging Protocol API
|
||||
</ul>
|
||||
|
||||
<h3>LIBRARY</h3><ul>
|
||||
RTMPDump RTMP (librtmp, -lrtmp)
|
||||
</ul>
|
||||
|
||||
<h3>SYNOPSIS</h3><ul>
|
||||
<b>#include <librtmp/rtmp.h></b>
|
||||
</ul>
|
||||
|
||||
<h3>DESCRIPTION</h3><ul>
|
||||
The Real-Time Messaging Protocol (RTMP) is used for streaming
|
||||
multimedia content across a TCP/IP network. This API provides most client
|
||||
functions and a few server functions needed to support RTMP, RTMP tunneled
|
||||
in HTTP (RTMPT), encrypted RTMP (RTMPE), RTMP over SSL/TLS (RTMPS) and
|
||||
tunneled variants of these encrypted types (RTMPTE, RTMPTS). The basic
|
||||
RTMP specification has been published by Adobe but this API was
|
||||
reverse-engineered without use of the Adobe specification. As such, it may
|
||||
deviate from any published specifications but it usually duplicates the
|
||||
actual behavior of the original Adobe clients.
|
||||
<p>
|
||||
The RTMPDump software package includes a basic client utility program
|
||||
in
|
||||
<a href="../man1/rtmpdump.1"><b>rtmpdump</b></a>(1),
|
||||
some sample servers, and a library used to provide programmatic access
|
||||
to the RTMP protocol. This man page gives an overview of the RTMP
|
||||
library routines. These routines are found in the -lrtmp library. Many
|
||||
other routines are also available, but they are not documented yet.
|
||||
<p>
|
||||
The basic interaction is as follows. A session handle is created using
|
||||
<b>RTMP_Alloc</b>()
|
||||
and initialized using
|
||||
<b>RTMP_Init</b>().
|
||||
All session parameters are provided using
|
||||
<b>RTMP_SetupURL</b>().
|
||||
The network connection is established using
|
||||
<b>RTMP_Connect</b>(),
|
||||
and then the RTMP session is established using
|
||||
<b>RTMP_ConnectStream</b>().
|
||||
The stream is read using
|
||||
<b>RTMP_Read</b>().
|
||||
A client can publish a stream by calling
|
||||
<b>RTMP_EnableWrite</b>()
|
||||
before the
|
||||
<b>RTMP_Connect</b>()
|
||||
call, and then using
|
||||
<b>RTMP_Write</b>()
|
||||
after the session is established.
|
||||
While a stream is playing it may be paused and unpaused using
|
||||
<b>RTMP_Pause</b>().
|
||||
The stream playback position can be moved using
|
||||
<b>RTMP_Seek</b>().
|
||||
When
|
||||
<b>RTMP_Read</b>()
|
||||
returns 0 bytes, the stream is complete and may be closed using
|
||||
<b>RTMP_Close</b>().
|
||||
The session handle is freed using
|
||||
<b>RTMP_Free</b>().
|
||||
<p>
|
||||
All data is transferred using FLV format. The basic session requires
|
||||
an RTMP URL. The RTMP URL format is of the form
|
||||
<pre>
|
||||
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
|
||||
</pre>
|
||||
<p>
|
||||
Plain rtmp, as well as tunneled and encrypted sessions are supported.
|
||||
<p>
|
||||
Additional options may be specified by appending space-separated
|
||||
key=value pairs to the URL. Special characters in values may need
|
||||
to be escaped to prevent misinterpretation by the option parser.
|
||||
The escape encoding uses a backslash followed by two hexadecimal digits
|
||||
representing the ASCII value of the character. E.g., spaces must
|
||||
be escaped as <b>\20</b> and backslashes must be escaped as <b>\5c</b>.
|
||||
</ul>
|
||||
|
||||
<h3>OPTIONS</h3><ul>
|
||||
</ul>
|
||||
|
||||
<h4>Network Parameters</h4><ul>
|
||||
These options define how to connect to the media server.
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>socks=</b><i>host:port</i>
|
||||
<dd>
|
||||
Use the specified SOCKS4 proxy.
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h4>Connection Parameters</h4><ul>
|
||||
These options define the content of the RTMP Connect request packet.
|
||||
If correct values are not provided, the media server will reject the
|
||||
connection attempt.
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>app=</b><i>name</i>
|
||||
<dd>
|
||||
Name of application to connect to on the RTMP server. Overrides
|
||||
the app in the RTMP URL. Sometimes the librtmp URL parser cannot
|
||||
determine the app name automatically, so it must be given explicitly
|
||||
using this option.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>tcUrl=</b><i>url</i>
|
||||
<dd>
|
||||
URL of the target stream. Defaults to rtmp[t][e|s]://host[:port]/app.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>pageUrl=</b><i>url</i>
|
||||
<dd>
|
||||
URL of the web page in which the media was embedded. By default no
|
||||
value will be sent.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>swfUrl=</b><i>url</i>
|
||||
<dd>
|
||||
URL of the SWF player for the media. By default no value will be sent.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>flashVer=</b><i>version</i>
|
||||
<dd>
|
||||
Version of the Flash plugin used to run the SWF player. The
|
||||
default is "LNX 10,0,32,18".
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>conn=</b><i>type:data</i>
|
||||
<dd>
|
||||
Append arbitrary AMF data to the Connect message. The type
|
||||
must be B for Boolean, N for number, S for string, O for object, or Z
|
||||
for null. For Booleans the data must be either 0 or 1 for FALSE or TRUE,
|
||||
respectively. Likewise for Objects the data must be 0 or 1 to end or
|
||||
begin an object, respectively. Data items in subobjects may be named, by
|
||||
prefixing the type with 'N' and specifying the name before the value, e.g.
|
||||
NB:myFlag:1. This option may be used multiple times to construct arbitrary
|
||||
AMF sequences. E.g.
|
||||
<pre>
|
||||
conn=B:1 conn=S:authMe conn=O:1 conn=NN:code:1.23 conn=NS:flag:ok conn=O:0
|
||||
</pre>
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h4>Session Parameters</h4><ul>
|
||||
These options take effect after the Connect request has succeeded.
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>playpath=</b><i>path</i>
|
||||
<dd>
|
||||
Overrides the playpath parsed from the RTMP URL. Sometimes the
|
||||
rtmpdump URL parser cannot determine the correct playpath
|
||||
automatically, so it must be given explicitly using this option.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>playlist=</b><i>0|1</i>
|
||||
<dd>
|
||||
If the value is 1 or TRUE, issue a set_playlist command before sending the
|
||||
play command. The playlist will just contain the current playpath. If the
|
||||
value is 0 or FALSE, the set_playlist command will not be sent. The
|
||||
default is FALSE.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>live=</b><i>0|1</i>
|
||||
<dd>
|
||||
Specify that the media is a live stream. No resuming or seeking in
|
||||
live streams is possible.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>subscribe=</b><i>path</i>
|
||||
<dd>
|
||||
Name of live stream to subscribe to. Defaults to
|
||||
<i>playpath</i>.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>start=</b><i>num</i>
|
||||
<dd>
|
||||
Start at
|
||||
<i>num</i>
|
||||
seconds into the stream. Not valid for live streams.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>stop=</b><i>num</i>
|
||||
<dd>
|
||||
Stop at
|
||||
<i>num</i>
|
||||
seconds into the stream.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>buffer=</b><i>num</i>
|
||||
<dd>
|
||||
Set buffer time to
|
||||
<i>num</i>
|
||||
milliseconds. The default is 30000.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>timeout=</b><i>num</i>
|
||||
<dd>
|
||||
Timeout the session after
|
||||
<i>num</i>
|
||||
seconds without receiving any data from the server. The default is 120.
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h4>Security Parameters</h4><ul>
|
||||
These options handle additional authentication requests from the server.
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>token=</b><i>key</i>
|
||||
<dd>
|
||||
Key for SecureToken response, used if the server requires SecureToken
|
||||
authentication.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>jtv=</b><i>JSON</i>
|
||||
<dd>
|
||||
JSON token used by legacy Justin.tv servers. Invokes NetStream.Authenticate.UsherToken
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>swfVfy=</b><i>0|1</i>
|
||||
<dd>
|
||||
If the value is 1 or TRUE, the SWF player is retrieved from the
|
||||
specified
|
||||
<i>swfUrl</i>
|
||||
for performing SWF Verification. The SWF hash and size (used in the
|
||||
verification step) are computed automatically. Also the SWF information is
|
||||
cached in a
|
||||
<i>.swfinfo</i>
|
||||
file in the user's home directory, so that it doesn't need to be retrieved
|
||||
and recalculated every time. The .swfinfo file records
|
||||
the SWF URL, the time it was fetched, the modification timestamp of the SWF
|
||||
file, its size, and its hash. By default, the cached info will be used
|
||||
for 30 days before re-checking.
|
||||
</dl>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>swfAge=</b><i>days</i>
|
||||
<dd>
|
||||
Specify how many days to use the cached SWF info before re-checking. Use
|
||||
0 to always check the SWF URL. Note that if the check shows that the
|
||||
SWF file has the same modification timestamp as before, it will not be
|
||||
retrieved again.
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h3>EXAMPLES</h3><ul>
|
||||
An example character string suitable for use with
|
||||
<b>RTMP_SetupURL</b>():
|
||||
<pre>
|
||||
"rtmp://flashserver:1935/ondemand/thefile swfUrl=<a href="http://flashserver/player.swf">http://flashserver/player.swf</a> swfVfy=1"
|
||||
</pre>
|
||||
</ul>
|
||||
|
||||
<h3>ENVIRONMENT</h3><ul>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<b>HOME</b>
|
||||
<dd>
|
||||
The value of
|
||||
$<b>HOME</b>
|
||||
is used as the location for the
|
||||
<i>.swfinfo</i>
|
||||
file.
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h3>FILES</h3><ul>
|
||||
<p>
|
||||
<dl compact><dt>
|
||||
<i>$HOME/.swfinfo</i>
|
||||
<dd>
|
||||
Cache of SWF Verification information
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
<h3>SEE ALSO</h3><ul>
|
||||
<a href="../man1/rtmpdump.1"><b>rtmpdump</b></a>(1),
|
||||
<a href="../man8/rtmpgw.8"><b>rtmpgw</b></a>(8)
|
||||
</ul>
|
||||
|
||||
<h3>AUTHORS</h3><ul>
|
||||
Andrej Stepanchuk, Howard Chu, The Flvstreamer Team
|
||||
<br>
|
||||
<<a href="http://rtmpdump.mplayerhq.hu">http://rtmpdump.mplayerhq.hu</a>>
|
||||
</ul></tbody></table></html>
|
|
@ -0,0 +1,13 @@
|
|||
prefix=@prefix@
|
||||
exec_prefix=${prefix}
|
||||
libdir=@libdir@
|
||||
incdir=${prefix}/include
|
||||
|
||||
Name: librtmp
|
||||
Description: RTMP implementation
|
||||
Version: @VERSION@
|
||||
Requires: @CRYPTO_REQ@
|
||||
URL: http://rtmpdump.mplayerhq.hu
|
||||
Libs: -L${libdir} -lrtmp -lz @PUBLIC_LIBS@
|
||||
Libs.private: @PRIVATE_LIBS@
|
||||
Cflags: -I${incdir}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (C) 2008-2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "rtmp_sys.h"
|
||||
#include "log.h"
|
||||
|
||||
#define MAX_PRINT_LEN 2048
|
||||
|
||||
RTMP_LogLevel RTMP_debuglevel = RTMP_LOGERROR;
|
||||
|
||||
static int neednl;
|
||||
|
||||
static FILE *fmsg;
|
||||
|
||||
static RTMP_LogCallback rtmp_log_default, *cb = rtmp_log_default;
|
||||
|
||||
static const char *levels[] = {
|
||||
"CRIT", "ERROR", "WARNING", "INFO",
|
||||
"DEBUG", "DEBUG2"
|
||||
};
|
||||
|
||||
static void rtmp_log_default(int level, const char *format, va_list vl)
|
||||
{
|
||||
char str[MAX_PRINT_LEN]="";
|
||||
|
||||
vsnprintf(str, MAX_PRINT_LEN-1, format, vl);
|
||||
|
||||
/* Filter out 'no-name' */
|
||||
if ( RTMP_debuglevel<RTMP_LOGALL && strstr(str, "no-name" ) != NULL )
|
||||
return;
|
||||
|
||||
if ( !fmsg ) fmsg = stderr;
|
||||
|
||||
if ( level <= RTMP_debuglevel ) {
|
||||
if (neednl) {
|
||||
putc('\n', fmsg);
|
||||
neednl = 0;
|
||||
}
|
||||
fprintf(fmsg, "%s: %s\n", levels[level], str);
|
||||
#ifdef _DEBUG
|
||||
fflush(fmsg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void RTMP_LogSetOutput(FILE *file)
|
||||
{
|
||||
fmsg = file;
|
||||
}
|
||||
|
||||
void RTMP_LogSetLevel(RTMP_LogLevel level)
|
||||
{
|
||||
RTMP_debuglevel = level;
|
||||
}
|
||||
|
||||
void RTMP_LogSetCallback(RTMP_LogCallback *cbp)
|
||||
{
|
||||
cb = cbp;
|
||||
}
|
||||
|
||||
RTMP_LogLevel RTMP_LogGetLevel()
|
||||
{
|
||||
return RTMP_debuglevel;
|
||||
}
|
||||
|
||||
void RTMP_Log(int level, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
if ( level > RTMP_debuglevel )
|
||||
return;
|
||||
|
||||
va_start(args, format);
|
||||
cb(level, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static const char hexdig[] = "0123456789abcdef";
|
||||
|
||||
void RTMP_LogHex(int level, const uint8_t *data, unsigned long len)
|
||||
{
|
||||
unsigned long i;
|
||||
char line[50], *ptr;
|
||||
|
||||
if ( level > RTMP_debuglevel )
|
||||
return;
|
||||
|
||||
ptr = line;
|
||||
|
||||
for(i=0; i<len; i++) {
|
||||
*ptr++ = hexdig[0x0f & (data[i] >> 4)];
|
||||
*ptr++ = hexdig[0x0f & data[i]];
|
||||
if ((i & 0x0f) == 0x0f) {
|
||||
*ptr = '\0';
|
||||
ptr = line;
|
||||
RTMP_Log(level, "%s", line);
|
||||
} else {
|
||||
*ptr++ = ' ';
|
||||
}
|
||||
}
|
||||
if (i & 0x0f) {
|
||||
*ptr = '\0';
|
||||
RTMP_Log(level, "%s", line);
|
||||
}
|
||||
}
|
||||
|
||||
void RTMP_LogHexString(int level, const uint8_t *data, unsigned long len)
|
||||
{
|
||||
#define BP_OFFSET 9
|
||||
#define BP_GRAPH 60
|
||||
#define BP_LEN 80
|
||||
char line[BP_LEN];
|
||||
unsigned long i;
|
||||
|
||||
if ( !data || level > RTMP_debuglevel )
|
||||
return;
|
||||
|
||||
/* in case len is zero */
|
||||
line[0] = '\0';
|
||||
|
||||
for ( i = 0 ; i < len ; i++ ) {
|
||||
int n = i % 16;
|
||||
unsigned off;
|
||||
|
||||
if( !n ) {
|
||||
if( i ) RTMP_Log( level, "%s", line );
|
||||
memset( line, ' ', sizeof(line)-2 );
|
||||
line[sizeof(line)-2] = '\0';
|
||||
|
||||
off = i % 0x0ffffU;
|
||||
|
||||
line[2] = hexdig[0x0f & (off >> 12)];
|
||||
line[3] = hexdig[0x0f & (off >> 8)];
|
||||
line[4] = hexdig[0x0f & (off >> 4)];
|
||||
line[5] = hexdig[0x0f & off];
|
||||
line[6] = ':';
|
||||
}
|
||||
|
||||
off = BP_OFFSET + n*3 + ((n >= 8)?1:0);
|
||||
line[off] = hexdig[0x0f & ( data[i] >> 4 )];
|
||||
line[off+1] = hexdig[0x0f & data[i]];
|
||||
|
||||
off = BP_GRAPH + n + ((n >= 8)?1:0);
|
||||
|
||||
if ( isprint( data[i] )) {
|
||||
line[BP_GRAPH + n] = data[i];
|
||||
} else {
|
||||
line[BP_GRAPH + n] = '.';
|
||||
}
|
||||
}
|
||||
|
||||
RTMP_Log( level, "%s", line );
|
||||
}
|
||||
|
||||
/* These should only be used by apps, never by the library itself */
|
||||
void RTMP_LogPrintf(const char *format, ...)
|
||||
{
|
||||
char str[MAX_PRINT_LEN]="";
|
||||
int len;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
len = vsnprintf(str, MAX_PRINT_LEN-1, format, args);
|
||||
va_end(args);
|
||||
|
||||
if ( RTMP_debuglevel==RTMP_LOGCRIT )
|
||||
return;
|
||||
|
||||
if ( !fmsg ) fmsg = stderr;
|
||||
|
||||
if (neednl) {
|
||||
putc('\n', fmsg);
|
||||
neednl = 0;
|
||||
}
|
||||
|
||||
if (len > MAX_PRINT_LEN-1)
|
||||
len = MAX_PRINT_LEN-1;
|
||||
fprintf(fmsg, "%s", str);
|
||||
if (str[len-1] == '\n')
|
||||
fflush(fmsg);
|
||||
}
|
||||
|
||||
void RTMP_LogStatus(const char *format, ...)
|
||||
{
|
||||
char str[MAX_PRINT_LEN]="";
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(str, MAX_PRINT_LEN-1, format, args);
|
||||
va_end(args);
|
||||
|
||||
if ( RTMP_debuglevel==RTMP_LOGCRIT )
|
||||
return;
|
||||
|
||||
if ( !fmsg ) fmsg = stderr;
|
||||
|
||||
fprintf(fmsg, "%s", str);
|
||||
fflush(fmsg);
|
||||
neednl = 1;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (C) 2008-2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#ifndef __RTMP_LOG_H__
|
||||
#define __RTMP_LOG_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/* Enable this to get full debugging output */
|
||||
/* #define _DEBUG */
|
||||
|
||||
#ifdef _DEBUG
|
||||
#undef NODEBUG
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{ RTMP_LOGCRIT=0, RTMP_LOGERROR, RTMP_LOGWARNING, RTMP_LOGINFO,
|
||||
RTMP_LOGDEBUG, RTMP_LOGDEBUG2, RTMP_LOGALL
|
||||
} RTMP_LogLevel;
|
||||
|
||||
extern RTMP_LogLevel RTMP_debuglevel;
|
||||
|
||||
typedef void (RTMP_LogCallback)(int level, const char *fmt, va_list);
|
||||
void RTMP_LogSetCallback(RTMP_LogCallback *cb);
|
||||
void RTMP_LogSetOutput(FILE *file);
|
||||
#ifdef __GNUC__
|
||||
void RTMP_LogPrintf(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
|
||||
void RTMP_LogStatus(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
|
||||
void RTMP_Log(int level, const char *format, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
|
||||
#else
|
||||
void RTMP_LogPrintf(const char *format, ...);
|
||||
void RTMP_LogStatus(const char *format, ...);
|
||||
void RTMP_Log(int level, const char *format, ...);
|
||||
#endif
|
||||
void RTMP_LogHex(int level, const uint8_t *data, unsigned long len);
|
||||
void RTMP_LogHexString(int level, const uint8_t *data, unsigned long len);
|
||||
void RTMP_LogSetLevel(RTMP_LogLevel lvl);
|
||||
RTMP_LogLevel RTMP_LogGetLevel(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Copyright (C) 2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "rtmp_sys.h"
|
||||
#include "log.h"
|
||||
#include "strncasecmp.h"
|
||||
|
||||
int RTMP_ParseURL(const char *url, int *protocol, AVal *host, unsigned int *port,
|
||||
AVal *playpath, AVal *app)
|
||||
{
|
||||
char *p, *end, *col, *ques, *slash;
|
||||
|
||||
RTMP_Log(RTMP_LOGDEBUG, "Parsing...");
|
||||
|
||||
*protocol = RTMP_PROTOCOL_RTMP;
|
||||
*port = 0;
|
||||
playpath->av_len = 0;
|
||||
playpath->av_val = NULL;
|
||||
app->av_len = 0;
|
||||
app->av_val = NULL;
|
||||
|
||||
/* Old School Parsing */
|
||||
|
||||
/* look for usual :// pattern */
|
||||
p = strstr(url, "://");
|
||||
if(!p) {
|
||||
RTMP_Log(RTMP_LOGERROR, "RTMP URL: No :// in url!");
|
||||
return FALSE;
|
||||
}
|
||||
{
|
||||
int len = (int)(p-url);
|
||||
|
||||
if(len == 4 && strncasecmp(url, "rtmp", 4)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMP;
|
||||
else if(len == 5 && strncasecmp(url, "rtmpt", 5)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMPT;
|
||||
else if(len == 5 && strncasecmp(url, "rtmps", 5)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMPS;
|
||||
else if(len == 5 && strncasecmp(url, "rtmpe", 5)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMPE;
|
||||
else if(len == 5 && strncasecmp(url, "rtmfp", 5)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMFP;
|
||||
else if(len == 6 && strncasecmp(url, "rtmpte", 6)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMPTE;
|
||||
else if(len == 6 && strncasecmp(url, "rtmpts", 6)==0)
|
||||
*protocol = RTMP_PROTOCOL_RTMPTS;
|
||||
else {
|
||||
RTMP_Log(RTMP_LOGWARNING, "Unknown protocol!\n");
|
||||
goto parsehost;
|
||||
}
|
||||
}
|
||||
|
||||
RTMP_Log(RTMP_LOGDEBUG, "Parsed protocol: %d", *protocol);
|
||||
|
||||
parsehost:
|
||||
/* let's get the hostname */
|
||||
p+=3;
|
||||
|
||||
/* check for sudden death */
|
||||
if(*p==0) {
|
||||
RTMP_Log(RTMP_LOGWARNING, "No hostname in URL!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
end = p + strlen(p);
|
||||
col = strchr(p, ':');
|
||||
ques = strchr(p, '?');
|
||||
slash = strchr(p, '/');
|
||||
|
||||
{
|
||||
int hostlen;
|
||||
if(slash)
|
||||
hostlen = slash - p;
|
||||
else
|
||||
hostlen = end - p;
|
||||
if(col && col -p < hostlen)
|
||||
hostlen = col - p;
|
||||
|
||||
if(hostlen < 256) {
|
||||
host->av_val = p;
|
||||
host->av_len = hostlen;
|
||||
RTMP_Log(RTMP_LOGDEBUG, "Parsed host : %.*s", hostlen, host->av_val);
|
||||
} else {
|
||||
RTMP_Log(RTMP_LOGWARNING, "Hostname exceeds 255 characters!");
|
||||
}
|
||||
|
||||
p+=hostlen;
|
||||
}
|
||||
|
||||
/* get the port number if available */
|
||||
if(*p == ':') {
|
||||
unsigned int p2;
|
||||
p++;
|
||||
p2 = atoi(p);
|
||||
if(p2 > 65535) {
|
||||
RTMP_Log(RTMP_LOGWARNING, "Invalid port number!");
|
||||
} else {
|
||||
*port = p2;
|
||||
}
|
||||
}
|
||||
|
||||
if(!slash) {
|
||||
RTMP_Log(RTMP_LOGWARNING, "No application or playpath in URL!");
|
||||
return TRUE;
|
||||
}
|
||||
p = slash+1;
|
||||
|
||||
{
|
||||
/* parse application
|
||||
*
|
||||
* rtmp://host[:port]/app[/appinstance][/...]
|
||||
* application = app[/appinstance]
|
||||
*/
|
||||
|
||||
char *slash2, *slash3 = NULL, *slash4 = NULL;
|
||||
int applen, appnamelen;
|
||||
|
||||
slash2 = strchr(p, '/');
|
||||
if(slash2)
|
||||
slash3 = strchr(slash2+1, '/');
|
||||
if(slash3)
|
||||
slash4 = strchr(slash3+1, '/');
|
||||
|
||||
applen = end-p; /* ondemand, pass all parameters as app */
|
||||
appnamelen = applen; /* ondemand length */
|
||||
|
||||
if(ques && strstr(p, "slist=")) { /* whatever it is, the '?' and slist= means we need to use everything as app and parse plapath from slist= */
|
||||
appnamelen = ques-p;
|
||||
}
|
||||
else if(strncmp(p, "ondemand/", 9)==0) {
|
||||
/* app = ondemand/foobar, only pass app=ondemand */
|
||||
applen = 8;
|
||||
appnamelen = 8;
|
||||
}
|
||||
else { /* app!=ondemand, so app is app[/appinstance] */
|
||||
if(slash4)
|
||||
appnamelen = slash4-p;
|
||||
else if(slash3)
|
||||
appnamelen = slash3-p;
|
||||
else if(slash2)
|
||||
appnamelen = slash2-p;
|
||||
|
||||
applen = appnamelen;
|
||||
}
|
||||
|
||||
app->av_val = p;
|
||||
app->av_len = applen;
|
||||
RTMP_Log(RTMP_LOGDEBUG, "Parsed app : %.*s", applen, p);
|
||||
|
||||
p += appnamelen;
|
||||
}
|
||||
|
||||
if (*p == '/')
|
||||
p++;
|
||||
|
||||
if (end-p) {
|
||||
AVal av = {p, end-p};
|
||||
RTMP_ParsePlaypath(&av, playpath);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extracts playpath from RTMP URL. playpath is the file part of the
|
||||
* URL, i.e. the part that comes after rtmp://host:port/app/
|
||||
*
|
||||
* Returns the stream name in a format understood by FMS. The name is
|
||||
* the playpath part of the URL with formatting depending on the stream
|
||||
* type:
|
||||
*
|
||||
* mp4 streams: prepend "mp4:", remove extension
|
||||
* mp3 streams: prepend "mp3:", remove extension
|
||||
* flv streams: remove extension
|
||||
*/
|
||||
void RTMP_ParsePlaypath(AVal *in, AVal *out) {
|
||||
int addMP4 = 0;
|
||||
int addMP3 = 0;
|
||||
int subExt = 0;
|
||||
const char *playpath = in->av_val;
|
||||
const char *temp, *q, *ext = NULL;
|
||||
const char *ppstart = playpath;
|
||||
char *streamname, *destptr, *p;
|
||||
|
||||
int pplen = in->av_len;
|
||||
|
||||
out->av_val = NULL;
|
||||
out->av_len = 0;
|
||||
|
||||
if ((*ppstart == '?') &&
|
||||
(temp=strstr(ppstart, "slist=")) != 0) {
|
||||
ppstart = temp+6;
|
||||
pplen = strlen(ppstart);
|
||||
|
||||
temp = strchr(ppstart, '&');
|
||||
if (temp) {
|
||||
pplen = temp-ppstart;
|
||||
}
|
||||
}
|
||||
|
||||
q = strchr(ppstart, '?');
|
||||
if (pplen >= 4) {
|
||||
if (q)
|
||||
ext = q-4;
|
||||
else
|
||||
ext = &ppstart[pplen-4];
|
||||
if ((strncmp(ext, ".f4v", 4) == 0) ||
|
||||
(strncmp(ext, ".mp4", 4) == 0)) {
|
||||
addMP4 = 1;
|
||||
subExt = 1;
|
||||
/* Only remove .flv from rtmp URL, not slist params */
|
||||
} else if ((ppstart == playpath) &&
|
||||
(strncmp(ext, ".flv", 4) == 0)) {
|
||||
subExt = 1;
|
||||
} else if (strncmp(ext, ".mp3", 4) == 0) {
|
||||
addMP3 = 1;
|
||||
subExt = 1;
|
||||
}
|
||||
}
|
||||
|
||||
streamname = (char *)malloc((pplen+4+1)*sizeof(char));
|
||||
if (!streamname)
|
||||
return;
|
||||
|
||||
destptr = streamname;
|
||||
if (addMP4) {
|
||||
if (strncmp(ppstart, "mp4:", 4)) {
|
||||
strcpy(destptr, "mp4:");
|
||||
destptr += 4;
|
||||
} else {
|
||||
subExt = 0;
|
||||
}
|
||||
} else if (addMP3) {
|
||||
if (strncmp(ppstart, "mp3:", 4)) {
|
||||
strcpy(destptr, "mp3:");
|
||||
destptr += 4;
|
||||
} else {
|
||||
subExt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (p=(char *)ppstart; pplen >0;) {
|
||||
/* skip extension */
|
||||
if (subExt && p == ext) {
|
||||
p += 4;
|
||||
pplen -= 4;
|
||||
continue;
|
||||
}
|
||||
if (*p == '%') {
|
||||
unsigned int c;
|
||||
sscanf(p+1, "%02x", &c);
|
||||
*destptr++ = c;
|
||||
pplen -= 3;
|
||||
p += 3;
|
||||
} else {
|
||||
*destptr++ = *p++;
|
||||
pplen--;
|
||||
}
|
||||
}
|
||||
*destptr = '\0';
|
||||
|
||||
out->av_val = streamname;
|
||||
out->av_len = destptr - streamname;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,426 @@
|
|||
#ifndef __RTMP_H__
|
||||
#define __RTMP_H__
|
||||
/*
|
||||
* Copyright (C) 2005-2008 Team XBMC
|
||||
* http://www.xbmc.org
|
||||
* Copyright (C) 2008-2009 Andrej Stepanchuk
|
||||
* Copyright (C) 2009-2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#if !defined(NO_CRYPTO) && !defined(CRYPTO)
|
||||
#define CRYPTO
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "amf.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define RTMP_LIB_VERSION 0x020300 /* 2.3 */
|
||||
|
||||
#define RTMP_FEATURE_HTTP 0x01
|
||||
#define RTMP_FEATURE_ENC 0x02
|
||||
#define RTMP_FEATURE_SSL 0x04
|
||||
#define RTMP_FEATURE_MFP 0x08 /* not yet supported */
|
||||
#define RTMP_FEATURE_WRITE 0x10 /* publish, not play */
|
||||
#define RTMP_FEATURE_HTTP2 0x20 /* server-side rtmpt */
|
||||
|
||||
#define RTMP_PROTOCOL_UNDEFINED -1
|
||||
#define RTMP_PROTOCOL_RTMP 0
|
||||
#define RTMP_PROTOCOL_RTMPE RTMP_FEATURE_ENC
|
||||
#define RTMP_PROTOCOL_RTMPT RTMP_FEATURE_HTTP
|
||||
#define RTMP_PROTOCOL_RTMPS RTMP_FEATURE_SSL
|
||||
#define RTMP_PROTOCOL_RTMPTE (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC)
|
||||
#define RTMP_PROTOCOL_RTMPTS (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL)
|
||||
#define RTMP_PROTOCOL_RTMFP RTMP_FEATURE_MFP
|
||||
|
||||
#define RTMP_DEFAULT_CHUNKSIZE 128
|
||||
|
||||
/* needs to fit largest number of bytes recv() may return */
|
||||
#define RTMP_BUFFER_CACHE_SIZE (16*1024)
|
||||
|
||||
#define RTMP_CHANNELS 65600
|
||||
|
||||
extern const char RTMPProtocolStringsLower[][7];
|
||||
extern const AVal RTMP_DefaultFlashVer;
|
||||
extern int RTMP_ctrlC;
|
||||
|
||||
uint32_t RTMP_GetTime(void);
|
||||
|
||||
/* RTMP_PACKET_TYPE_... 0x00 */
|
||||
#define RTMP_PACKET_TYPE_CHUNK_SIZE 0x01
|
||||
/* RTMP_PACKET_TYPE_... 0x02 */
|
||||
#define RTMP_PACKET_TYPE_BYTES_READ_REPORT 0x03
|
||||
#define RTMP_PACKET_TYPE_CONTROL 0x04
|
||||
#define RTMP_PACKET_TYPE_SERVER_BW 0x05
|
||||
#define RTMP_PACKET_TYPE_CLIENT_BW 0x06
|
||||
/* RTMP_PACKET_TYPE_... 0x07 */
|
||||
#define RTMP_PACKET_TYPE_AUDIO 0x08
|
||||
#define RTMP_PACKET_TYPE_VIDEO 0x09
|
||||
/* RTMP_PACKET_TYPE_... 0x0A */
|
||||
/* RTMP_PACKET_TYPE_... 0x0B */
|
||||
/* RTMP_PACKET_TYPE_... 0x0C */
|
||||
/* RTMP_PACKET_TYPE_... 0x0D */
|
||||
/* RTMP_PACKET_TYPE_... 0x0E */
|
||||
#define RTMP_PACKET_TYPE_FLEX_STREAM_SEND 0x0F
|
||||
#define RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT 0x10
|
||||
#define RTMP_PACKET_TYPE_FLEX_MESSAGE 0x11
|
||||
#define RTMP_PACKET_TYPE_INFO 0x12
|
||||
#define RTMP_PACKET_TYPE_SHARED_OBJECT 0x13
|
||||
#define RTMP_PACKET_TYPE_INVOKE 0x14
|
||||
/* RTMP_PACKET_TYPE_... 0x15 */
|
||||
#define RTMP_PACKET_TYPE_FLASH_VIDEO 0x16
|
||||
|
||||
#define RTMP_MAX_HEADER_SIZE 18
|
||||
|
||||
#define RTMP_PACKET_SIZE_LARGE 0
|
||||
#define RTMP_PACKET_SIZE_MEDIUM 1
|
||||
#define RTMP_PACKET_SIZE_SMALL 2
|
||||
#define RTMP_PACKET_SIZE_MINIMUM 3
|
||||
|
||||
typedef struct RTMPChunk
|
||||
{
|
||||
int c_headerSize;
|
||||
int c_chunkSize;
|
||||
char *c_chunk;
|
||||
char c_header[RTMP_MAX_HEADER_SIZE];
|
||||
} RTMPChunk;
|
||||
|
||||
typedef struct RTMPPacket
|
||||
{
|
||||
uint8_t m_headerType;
|
||||
uint8_t m_packetType;
|
||||
uint8_t m_hasAbsTimestamp; /* timestamp absolute or relative? */
|
||||
int m_nChannel;
|
||||
uint32_t m_nTimeStamp; /* timestamp */
|
||||
int32_t m_nInfoField2; /* last 4 bytes in a long header */
|
||||
uint32_t m_nBodySize;
|
||||
uint32_t m_nBytesRead;
|
||||
RTMPChunk *m_chunk;
|
||||
char *m_body;
|
||||
} RTMPPacket;
|
||||
|
||||
typedef struct RTMPSockBuf
|
||||
{
|
||||
int sb_socket;
|
||||
int sb_size; /* number of unprocessed bytes in buffer */
|
||||
char *sb_start; /* pointer into sb_pBuffer of next byte to process */
|
||||
char sb_buf[RTMP_BUFFER_CACHE_SIZE]; /* data read from socket */
|
||||
int sb_timedout;
|
||||
void *sb_ssl;
|
||||
} RTMPSockBuf;
|
||||
|
||||
void RTMPPacket_Reset(RTMPPacket *p);
|
||||
void RTMPPacket_Dump(RTMPPacket *p);
|
||||
int RTMPPacket_Alloc(RTMPPacket *p, uint32_t nSize);
|
||||
void RTMPPacket_Free(RTMPPacket *p);
|
||||
|
||||
#define RTMPPacket_IsReady(a) ((a)->m_nBytesRead == (a)->m_nBodySize)
|
||||
|
||||
typedef struct RTMP_LNK
|
||||
{
|
||||
AVal hostname;
|
||||
AVal sockshost;
|
||||
|
||||
AVal playpath0; /* parsed from URL */
|
||||
AVal playpath; /* passed in explicitly */
|
||||
AVal tcUrl;
|
||||
AVal swfUrl;
|
||||
AVal pageUrl;
|
||||
AVal app;
|
||||
AVal auth;
|
||||
AVal flashVer;
|
||||
AVal subscribepath;
|
||||
AVal usherToken;
|
||||
AVal token;
|
||||
AVal pubUser;
|
||||
AVal pubPasswd;
|
||||
AMFObject extras;
|
||||
int edepth;
|
||||
|
||||
int seekTime;
|
||||
int stopTime;
|
||||
|
||||
#define RTMP_LF_AUTH 0x0001 /* using auth param */
|
||||
#define RTMP_LF_LIVE 0x0002 /* stream is live */
|
||||
#define RTMP_LF_SWFV 0x0004 /* do SWF verification */
|
||||
#define RTMP_LF_PLST 0x0008 /* send playlist before play */
|
||||
#define RTMP_LF_BUFX 0x0010 /* toggle stream on BufferEmpty msg */
|
||||
#define RTMP_LF_FTCU 0x0020 /* free tcUrl on close */
|
||||
#define RTMP_LF_FAPU 0x0040 /* free app on close */
|
||||
int lFlags;
|
||||
|
||||
int swfAge;
|
||||
|
||||
int protocol;
|
||||
int timeout; /* connection timeout in seconds */
|
||||
|
||||
int pFlags; /* unused, but kept to avoid breaking ABI */
|
||||
|
||||
unsigned short socksport;
|
||||
unsigned short port;
|
||||
|
||||
#ifdef CRYPTO
|
||||
#define RTMP_SWF_HASHLEN 32
|
||||
void *dh; /* for encryption */
|
||||
void *rc4keyIn;
|
||||
void *rc4keyOut;
|
||||
|
||||
uint32_t SWFSize;
|
||||
uint8_t SWFHash[RTMP_SWF_HASHLEN];
|
||||
char SWFVerificationResponse[RTMP_SWF_HASHLEN+10];
|
||||
#endif
|
||||
} RTMP_LNK;
|
||||
|
||||
/* state for read() wrapper */
|
||||
typedef struct RTMP_READ
|
||||
{
|
||||
char *buf;
|
||||
char *bufpos;
|
||||
unsigned int buflen;
|
||||
uint32_t timestamp;
|
||||
uint8_t dataType;
|
||||
uint8_t flags;
|
||||
#define RTMP_READ_HEADER 0x01
|
||||
#define RTMP_READ_RESUME 0x02
|
||||
#define RTMP_READ_NO_IGNORE 0x04
|
||||
#define RTMP_READ_GOTKF 0x08
|
||||
#define RTMP_READ_GOTFLVK 0x10
|
||||
#define RTMP_READ_SEEKING 0x20
|
||||
int8_t status;
|
||||
#define RTMP_READ_COMPLETE -3
|
||||
#define RTMP_READ_ERROR -2
|
||||
#define RTMP_READ_EOF -1
|
||||
#define RTMP_READ_IGNORE 0
|
||||
|
||||
/* if bResume == TRUE */
|
||||
uint8_t initialFrameType;
|
||||
uint32_t nResumeTS;
|
||||
char *metaHeader;
|
||||
char *initialFrame;
|
||||
uint32_t nMetaHeaderSize;
|
||||
uint32_t nInitialFrameSize;
|
||||
uint32_t nIgnoredFrameCounter;
|
||||
uint32_t nIgnoredFlvFrameCounter;
|
||||
} RTMP_READ;
|
||||
|
||||
typedef struct RTMP_METHOD
|
||||
{
|
||||
AVal name;
|
||||
int num;
|
||||
} RTMP_METHOD;
|
||||
|
||||
typedef struct RTMP
|
||||
{
|
||||
int m_inChunkSize;
|
||||
int m_outChunkSize;
|
||||
int m_nBWCheckCounter;
|
||||
int m_nBytesIn;
|
||||
int m_nBytesInSent;
|
||||
int m_nBufferMS;
|
||||
int m_stream_id; /* returned in _result from createStream */
|
||||
int m_mediaChannel;
|
||||
uint32_t m_mediaStamp;
|
||||
uint32_t m_pauseStamp;
|
||||
int m_pausing;
|
||||
int m_nServerBW;
|
||||
int m_nClientBW;
|
||||
uint8_t m_nClientBW2;
|
||||
uint8_t m_bPlaying;
|
||||
uint8_t m_bSendEncoding;
|
||||
uint8_t m_bSendCounter;
|
||||
|
||||
int m_numInvokes;
|
||||
int m_numCalls;
|
||||
RTMP_METHOD *m_methodCalls; /* remote method calls queue */
|
||||
|
||||
int m_channelsAllocatedIn;
|
||||
int m_channelsAllocatedOut;
|
||||
RTMPPacket **m_vecChannelsIn;
|
||||
RTMPPacket **m_vecChannelsOut;
|
||||
int *m_channelTimestamp; /* abs timestamp of last packet */
|
||||
|
||||
double m_fAudioCodecs; /* audioCodecs for the connect packet */
|
||||
double m_fVideoCodecs; /* videoCodecs for the connect packet */
|
||||
double m_fEncoding; /* AMF0 or AMF3 */
|
||||
|
||||
double m_fDuration; /* duration of stream in seconds */
|
||||
|
||||
int m_msgCounter; /* RTMPT stuff */
|
||||
int m_polling;
|
||||
int m_resplen;
|
||||
int m_unackd;
|
||||
AVal m_clientID;
|
||||
|
||||
RTMP_READ m_read;
|
||||
RTMPPacket m_write;
|
||||
RTMPSockBuf m_sb;
|
||||
RTMP_LNK Link;
|
||||
} RTMP;
|
||||
|
||||
int RTMP_ParseURL(const char *url, int *protocol, AVal *host,
|
||||
unsigned int *port, AVal *playpath, AVal *app);
|
||||
|
||||
void RTMP_ParsePlaypath(AVal *in, AVal *out);
|
||||
void RTMP_SetBufferMS(RTMP *r, int size);
|
||||
void RTMP_UpdateBufferMS(RTMP *r);
|
||||
|
||||
int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);
|
||||
int RTMP_SetupURL(RTMP *r, char *url);
|
||||
void RTMP_SetupStream(RTMP *r, int protocol,
|
||||
AVal *hostname,
|
||||
unsigned int port,
|
||||
AVal *sockshost,
|
||||
AVal *playpath,
|
||||
AVal *tcUrl,
|
||||
AVal *swfUrl,
|
||||
AVal *pageUrl,
|
||||
AVal *app,
|
||||
AVal *auth,
|
||||
AVal *swfSHA256Hash,
|
||||
uint32_t swfSize,
|
||||
AVal *flashVer,
|
||||
AVal *subscribepath,
|
||||
AVal *usherToken,
|
||||
int dStart,
|
||||
int dStop, int bLiveStream, long int timeout);
|
||||
|
||||
int RTMP_Connect(RTMP *r, RTMPPacket *cp);
|
||||
struct sockaddr;
|
||||
int RTMP_Connect0(RTMP *r, struct sockaddr *svc);
|
||||
int RTMP_Connect1(RTMP *r, RTMPPacket *cp);
|
||||
int RTMP_Serve(RTMP *r);
|
||||
int RTMP_TLS_Accept(RTMP *r, void *ctx);
|
||||
|
||||
int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet);
|
||||
int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue);
|
||||
int RTMP_SendChunk(RTMP *r, RTMPChunk *chunk);
|
||||
int RTMP_IsConnected(RTMP *r);
|
||||
int RTMP_Socket(RTMP *r);
|
||||
int RTMP_IsTimedout(RTMP *r);
|
||||
double RTMP_GetDuration(RTMP *r);
|
||||
int RTMP_ToggleStream(RTMP *r);
|
||||
|
||||
int RTMP_ConnectStream(RTMP *r, int seekTime);
|
||||
int RTMP_ReconnectStream(RTMP *r, int seekTime);
|
||||
void RTMP_DeleteStream(RTMP *r);
|
||||
int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet);
|
||||
int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet);
|
||||
|
||||
void RTMP_Init(RTMP *r);
|
||||
void RTMP_Close(RTMP *r);
|
||||
RTMP *RTMP_Alloc(void);
|
||||
void RTMP_Free(RTMP *r);
|
||||
void RTMP_EnableWrite(RTMP *r);
|
||||
|
||||
void *RTMP_TLS_AllocServerContext(const char* cert, const char* key);
|
||||
void RTMP_TLS_FreeServerContext(void *ctx);
|
||||
|
||||
int RTMP_LibVersion(void);
|
||||
void RTMP_UserInterrupt(void); /* user typed Ctrl-C */
|
||||
|
||||
int RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject,
|
||||
unsigned int nTime);
|
||||
|
||||
/* caller probably doesn't know current timestamp, should
|
||||
* just use RTMP_Pause instead
|
||||
*/
|
||||
int RTMP_SendPause(RTMP *r, int DoPause, int dTime);
|
||||
int RTMP_Pause(RTMP *r, int DoPause);
|
||||
|
||||
int RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name,
|
||||
AMFObjectProperty * p);
|
||||
|
||||
int RTMPSockBuf_Fill(RTMPSockBuf *sb);
|
||||
int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len);
|
||||
int RTMPSockBuf_Close(RTMPSockBuf *sb);
|
||||
|
||||
int RTMP_SendCreateStream(RTMP *r);
|
||||
int RTMP_SendSeek(RTMP *r, int dTime);
|
||||
int RTMP_SendServerBW(RTMP *r);
|
||||
int RTMP_SendClientBW(RTMP *r);
|
||||
void RTMP_DropRequest(RTMP *r, int i, int freeit);
|
||||
int RTMP_Read(RTMP *r, char *buf, int size);
|
||||
int RTMP_Write(RTMP *r, const char *buf, int size);
|
||||
|
||||
/* hashswf.c */
|
||||
int RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
|
||||
int age);
|
||||
|
||||
/*
|
||||
***********************************************************************
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
* Introduced by SRS, export the ip/pid/cid of BMS
|
||||
***********************************************************************
|
||||
*/
|
||||
/*
|
||||
* The exported ip of server, for example, we use DNS to connect to server,
|
||||
* but the ip resolved by DNS system maybe virtual ip, that is, the "real ip"
|
||||
* only known by server itself and return by the rtmp connect result or flv
|
||||
* metadata.
|
||||
*/
|
||||
extern char* _srs_ip;
|
||||
/*
|
||||
* The pid of BMS, used to query the detail log of client.
|
||||
* A BMS server may restart and the pid changed.
|
||||
*/
|
||||
extern int _srs_pid;
|
||||
/*
|
||||
* The cid of BMS, used to query the detail log of client.
|
||||
* A connection of a process(identify by pid) is unique and its id(cid) is
|
||||
* unique also. The cid generally is a thread or connection or logic unit,
|
||||
* for example, cid of rtmp client is the rtmp connection, while cid of hls+
|
||||
* is a virtual connection which merge many http connections.
|
||||
*/
|
||||
extern int _srs_cid;
|
||||
/*
|
||||
***********************************************************************
|
||||
* Introduced by SRS, other useful data.
|
||||
***********************************************************************
|
||||
*/
|
||||
/*
|
||||
* The received bytes from server. user can use to stat the kbps by:
|
||||
* rkbps = rbytes * 8 / 1000 / (diff seconds)
|
||||
*/
|
||||
extern unsigned long _srs_rbytes;
|
||||
/*
|
||||
* The sent bytes from server. user can use to stat the kbps by:
|
||||
* skbps = sbytes * 8 / 1000 / (diff seconds)
|
||||
*/
|
||||
extern unsigned long _srs_sbytes;
|
||||
/*
|
||||
* The current state of client.
|
||||
* 0,init 1,idle 2,connected 3,working 4,closed
|
||||
*/
|
||||
extern int _srs_state;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,138 @@
|
|||
#ifndef __RTMP_SYS_H__
|
||||
#define __RTMP_SYS_H__
|
||||
/*
|
||||
* Copyright (C) 2010 Howard Chu
|
||||
*
|
||||
* This file is part of librtmp.
|
||||
*
|
||||
* librtmp is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* librtmp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with librtmp see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#if _MSC_VER < 1500 /* MSVC */
|
||||
#define snprintf _snprintf
|
||||
#define strcasecmp stricmp
|
||||
#define strncasecmp strnicmp
|
||||
#define vsnprintf _vsnprintf
|
||||
#endif
|
||||
|
||||
#define GetSockError() WSAGetLastError()
|
||||
#define SetSockError(e) WSASetLastError(e)
|
||||
#define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e)
|
||||
#define EWOULDBLOCK WSAETIMEDOUT /* we don't use nonblocking, but we do use timeouts */
|
||||
#define msleep(n) Sleep(n)
|
||||
#define SET_RCVTIMEO(tv,s) int tv = s*1000
|
||||
#else /* !_WIN32 */
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/times.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#define GetSockError() errno
|
||||
#define SetSockError(e) errno = e
|
||||
#undef closesocket
|
||||
#define closesocket(s) close(s)
|
||||
#define msleep(n) usleep(n*1000)
|
||||
#define SET_RCVTIMEO(tv,s) struct timeval tv = {s,0}
|
||||
#endif
|
||||
|
||||
#include "rtmp.h"
|
||||
|
||||
#ifdef USE_POLARSSL
|
||||
#include <polarssl/version.h>
|
||||
#include <polarssl/net.h>
|
||||
#include <polarssl/ssl.h>
|
||||
#include <polarssl/havege.h>
|
||||
#if POLARSSL_VERSION_NUMBER < 0x01010000
|
||||
#define havege_random havege_rand
|
||||
#endif
|
||||
#if POLARSSL_VERSION_NUMBER >= 0x01020000
|
||||
#define SSL_SET_SESSION(S,resume,timeout,ctx) ssl_set_session(S,ctx)
|
||||
#else
|
||||
#define SSL_SET_SESSION(S,resume,timeout,ctx) ssl_set_session(S,resume,timeout,ctx)
|
||||
#endif
|
||||
typedef struct tls_ctx {
|
||||
havege_state hs;
|
||||
ssl_session ssn;
|
||||
} tls_ctx;
|
||||
typedef struct tls_server_ctx {
|
||||
havege_state *hs;
|
||||
x509_cert cert;
|
||||
rsa_context key;
|
||||
ssl_session ssn;
|
||||
const char *dhm_P, *dhm_G;
|
||||
} tls_server_ctx;
|
||||
|
||||
#define TLS_CTX tls_ctx *
|
||||
#define TLS_client(ctx,s) s = malloc(sizeof(ssl_context)); ssl_init(s);\
|
||||
ssl_set_endpoint(s, SSL_IS_CLIENT); ssl_set_authmode(s, SSL_VERIFY_NONE);\
|
||||
ssl_set_rng(s, havege_random, &ctx->hs);\
|
||||
ssl_set_ciphersuites(s, ssl_default_ciphersuites);\
|
||||
SSL_SET_SESSION(s, 1, 600, &ctx->ssn)
|
||||
#define TLS_server(ctx,s) s = malloc(sizeof(ssl_context)); ssl_init(s);\
|
||||
ssl_set_endpoint(s, SSL_IS_SERVER); ssl_set_authmode(s, SSL_VERIFY_NONE);\
|
||||
ssl_set_rng(s, havege_random, ((tls_server_ctx*)ctx)->hs);\
|
||||
ssl_set_ciphersuites(s, ssl_default_ciphersuites);\
|
||||
SSL_SET_SESSION(s, 1, 600, &((tls_server_ctx*)ctx)->ssn);\
|
||||
ssl_set_own_cert(s, &((tls_server_ctx*)ctx)->cert, &((tls_server_ctx*)ctx)->key);\
|
||||
ssl_set_dh_param(s, ((tls_server_ctx*)ctx)->dhm_P, ((tls_server_ctx*)ctx)->dhm_G)
|
||||
#define TLS_setfd(s,fd) ssl_set_bio(s, net_recv, &fd, net_send, &fd)
|
||||
#define TLS_connect(s) ssl_handshake(s)
|
||||
#define TLS_accept(s) ssl_handshake(s)
|
||||
#define TLS_read(s,b,l) ssl_read(s,(unsigned char *)b,l)
|
||||
#define TLS_write(s,b,l) ssl_write(s,(unsigned char *)b,l)
|
||||
#define TLS_shutdown(s) ssl_close_notify(s)
|
||||
#define TLS_close(s) ssl_free(s); free(s)
|
||||
|
||||
#elif defined(USE_GNUTLS)
|
||||
#include <gnutls/gnutls.h>
|
||||
typedef struct tls_ctx {
|
||||
gnutls_certificate_credentials_t cred;
|
||||
gnutls_priority_t prios;
|
||||
} tls_ctx;
|
||||
#define TLS_CTX tls_ctx *
|
||||
#define TLS_client(ctx,s) gnutls_init((gnutls_session_t *)(&s), GNUTLS_CLIENT); gnutls_priority_set(s, ctx->prios); gnutls_credentials_set(s, GNUTLS_CRD_CERTIFICATE, ctx->cred)
|
||||
#define TLS_server(ctx,s) gnutls_init((gnutls_session_t *)(&s), GNUTLS_SERVER); gnutls_priority_set_direct(s, "NORMAL", NULL); gnutls_credentials_set(s, GNUTLS_CRD_CERTIFICATE, ctx)
|
||||
#define TLS_setfd(s,fd) gnutls_transport_set_ptr(s, (gnutls_transport_ptr_t)(long)fd)
|
||||
#define TLS_connect(s) gnutls_handshake(s)
|
||||
#define TLS_accept(s) gnutls_handshake(s)
|
||||
#define TLS_read(s,b,l) gnutls_record_recv(s,b,l)
|
||||
#define TLS_write(s,b,l) gnutls_record_send(s,b,l)
|
||||
#define TLS_shutdown(s) gnutls_bye(s, GNUTLS_SHUT_RDWR)
|
||||
#define TLS_close(s) gnutls_deinit(s)
|
||||
|
||||
#else /* USE_OPENSSL */
|
||||
#define TLS_CTX SSL_CTX *
|
||||
#define TLS_client(ctx,s) s = SSL_new(ctx)
|
||||
#define TLS_server(ctx,s) s = SSL_new(ctx)
|
||||
#define TLS_setfd(s,fd) SSL_set_fd(s,fd)
|
||||
#define TLS_connect(s) SSL_connect(s)
|
||||
#define TLS_accept(s) SSL_accept(s)
|
||||
#define TLS_read(s,b,l) SSL_read(s,b,l)
|
||||
#define TLS_write(s,b,l) SSL_write(s,b,l)
|
||||
#define TLS_shutdown(s) SSL_shutdown(s)
|
||||
#define TLS_close(s) SSL_free(s)
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef STRNCASECMP_H
|
||||
#define STRNCASECMP_H
|
||||
|
||||
|
||||
#ifndef WINSHIT_INCLUDED
|
||||
#define WINSHIT_INCLUDED
|
||||
|
||||
#if defined(WIN32) || defined(WIN64)
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp(x,y,z) _strnicmp(x,y,z)
|
||||
|
||||
#endif /* Def WIN32 or Def WIN64 */
|
||||
|
||||
#endif /* Ndef WINSHIT_INCLUDED */
|
||||
|
||||
|
||||
#endif // STRNCASECMP_H
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,47 @@
|
|||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
#include "cplaywidget.h"
|
||||
#include <QTimer>
|
||||
#include "CameraCapture.h"
|
||||
#include "mainwindow.h"
|
||||
#include <qlibrary.h>
|
||||
#include <qsysinfo.h>
|
||||
#include <qt_windows.h>
|
||||
#include "media/screen_capture.h"
|
||||
#include "media/DXGICapture.h"
|
||||
#include <QVector>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <shlobj.h>
|
||||
#include <D3D9.h>
|
||||
|
||||
#if _MSC_VER >= 1600
|
||||
#pragma execution_character_set("utf-8")
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <Tlhelp32.h>
|
||||
#include "winuser.h"
|
||||
#endif
|
||||
|
||||
int RegiesterOwnType(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
setbuf(stdout, NULL);//让printf立即输出
|
||||
ScreenCapture p;
|
||||
p.EnumScreen();
|
||||
|
||||
Direct3D9TakeScreenshots(0,4);
|
||||
QssEventFilter filter;
|
||||
QApplication app(argc, argv);
|
||||
|
||||
MainWindow main;
|
||||
|
||||
main.setWindowTitle("流媒体测试工具");
|
||||
main.setFixedSize(1920,1080);
|
||||
main.show();
|
||||
return app.exec();
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include <QDesktopWidget>
|
||||
#include <QPaintDevice>
|
||||
|
||||
#if _MSC_VER >= 1600
|
||||
#pragma execution_character_set("utf-8")
|
||||
#endif
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QssMainWindow(parent,0,1.5),
|
||||
ui(new Ui::MainWindow),
|
||||
m_bCameraOpen(false),
|
||||
mCamera(nullptr),
|
||||
m_bRtmpPushing(false),
|
||||
mPlayerWidget(nullptr),
|
||||
mVideoCoder(nullptr),
|
||||
mPusher(nullptr),
|
||||
mAudioCapture(nullptr),
|
||||
mTimer(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
this->move(50,50);
|
||||
int i(0);
|
||||
QDesktopWidget* desktopWidget = QApplication::desktop();
|
||||
QRect clientRect = desktopWidget->availableGeometry();
|
||||
QRect applicationRect = desktopWidget->screenGeometry();
|
||||
|
||||
qDebug()<<this->pos()<<clientRect<<applicationRect<<this->m_frame->geometry()<<this->centralWidget()->geometry();
|
||||
|
||||
std::vector<std::wstring> cameras = Camera::EnumAllCamera();
|
||||
for(std::wstring x : cameras){
|
||||
ui->comboBox->addItem(QString::fromWCharArray(x.c_str(),x.size()),
|
||||
QString::fromWCharArray(x.c_str(),x.size()));
|
||||
}
|
||||
mAudioCapture = new CaptureAudioFfmpeg(44100, 2);
|
||||
mMic = mAudioCapture->EnumSpeakers();
|
||||
qDebug()<<"capture "<<mMic.size()<<"mic";
|
||||
for(vector<CaptureAudioFfmpeg::MICInfo>::iterator itr = mMic.begin();itr != mMic.end();itr++){
|
||||
qDebug()<<QString::fromStdWString(itr->name)<<itr->index;
|
||||
|
||||
}
|
||||
|
||||
mTimer = new QTimer(this);
|
||||
connect(mTimer, SIGNAL(timeout()), this, SLOT(DetectDpi()));
|
||||
mTimer->start(100);
|
||||
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow(){
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_clicked(){
|
||||
if(nullptr == mPlayerWidget){
|
||||
mPlayerWidget = new CPlayWidget(nullptr);
|
||||
}
|
||||
if(!m_bCameraOpen){
|
||||
mPlayerWidget->SetDataType(CPlayWidget::IMG_TYPE::TYPE_RGB32);
|
||||
mPlayerWidget->SetImgSize(640,480);
|
||||
|
||||
qDebug()<<ui->comboBox->currentText().size()<<ui->comboBox->currentText();
|
||||
wchar_t *opencamera = new wchar_t[ui->comboBox->currentText().size()];
|
||||
ui->comboBox->currentText().toWCharArray(opencamera);
|
||||
wstring ss = wstring(opencamera,ui->comboBox->currentText().size());
|
||||
if(nullptr == mCamera){
|
||||
this->mCamera = new Camera(ss);
|
||||
}
|
||||
this->mCamera->SetObserver(mPlayerWidget);
|
||||
qDebug()<<ui->comboBox->currentText();
|
||||
ui->pushButton->setText("关闭摄像头");
|
||||
m_bCameraOpen = true;
|
||||
mPlayerWidget->show();
|
||||
ui->verticalLayout->addWidget(mPlayerWidget);
|
||||
qDebug()<<ui->verticalLayout->layout();
|
||||
ui->verticalLayout->setStretch(0,1);
|
||||
ui->verticalLayout->setStretch(1,0);
|
||||
ui->verticalLayout->setStretch(2,9);
|
||||
|
||||
} else{
|
||||
m_bCameraOpen = false;
|
||||
ui->pushButton->setText("打开摄像头");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_2_clicked()
|
||||
{
|
||||
if(!m_bRtmpPushing){
|
||||
if(!m_bCameraOpen){
|
||||
ToastWidget::showTip("请打开摄像头",this);
|
||||
return;
|
||||
}else{
|
||||
//
|
||||
if(nullptr == mVideoCoder){
|
||||
mVideoCoder = new VideoCoder(mCamera->GetWidth(),
|
||||
mCamera->GetHeight(),
|
||||
GUIDToAvFormat(mCamera->MediaType()));
|
||||
}
|
||||
mCamera->SetObserver(mVideoCoder);
|
||||
|
||||
// todo 根据返回结果判断是否推流
|
||||
qDebug()<<"连接RTMP服务器"<<ui->lineEdit->text();
|
||||
if (!mPusher->IfConnect()) {
|
||||
const char* address = ui->lineEdit->text().toLocal8Bit().data();
|
||||
qDebug()<<address;
|
||||
if (0 == mPusher->RTMP264_Connect("rtmp://127.0.0.1:1939/live/1")) {
|
||||
ToastWidget::showTip("已经连接上RTMP服务器",this->parentWidget());
|
||||
mVideoCoder->SetOberver(mPusher);
|
||||
mPusher->StartPush();
|
||||
ui->pushButton_2->setText("关闭推流");
|
||||
/*
|
||||
if (nullptr != this->mAudioCoder) {
|
||||
this->mAudioCoder->SetObserver(mPusher);
|
||||
//音频流先不推流
|
||||
}*/
|
||||
}
|
||||
else {
|
||||
ToastWidget::showTip("连接RTMP服务器失败,请检查服务器地址",this->parentWidget());
|
||||
}
|
||||
}else{
|
||||
ToastWidget::showTip("正在推流,请先关闭",this->parentWidget());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_3_clicked()
|
||||
{
|
||||
qDebug()<<ui->comboBox_2->currentText();
|
||||
}
|
||||
|
||||
void MainWindow::DetectDpi()
|
||||
{
|
||||
// qDebug()<<"detect dpi";
|
||||
int horizontalDPI = logicalDpiX();
|
||||
int verticalDPI = logicalDpiY();
|
||||
|
||||
// qDebug()<<horizontalDPI<<verticalDPI<<physicalDpiX()<<physicalDpiY();
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include "media/CameraCapture.h"
|
||||
#include "cplaywidget.h"
|
||||
#include "media/VideoCoder.h"
|
||||
#include "media/RtmpPusher.h"
|
||||
#include "components/toast.h"
|
||||
#include "utils.h"
|
||||
#include "Qss.h"
|
||||
#include "media/audiocaptureff.h"
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QssMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void on_pushButton_clicked();
|
||||
void on_pushButton_2_clicked();
|
||||
void on_pushButton_3_clicked();
|
||||
void DetectDpi();
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
Camera *mCamera;
|
||||
QStringList mCameraList;
|
||||
bool m_bCameraOpen;
|
||||
CPlayWidget *mPlayerWidget;
|
||||
VideoCoder *mVideoCoder;
|
||||
bool m_bRtmpPushing;
|
||||
H264RtmpPuser *mPusher;
|
||||
CaptureAudioFfmpeg *mAudioCapture;
|
||||
vector<CaptureAudioFfmpeg::MICInfo> mMic;
|
||||
QTimer *mTimer;
|
||||
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1383</width>
|
||||
<height>1116</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>800</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,9">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,2,0,0,0,0,0,13">
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="sizeIncrement">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>6</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>???????</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>??????</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox_2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>rtmp???????</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>rtmp://127.0.0.1:1935/live/1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>????</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,145 @@
|
|||
#include "AACAudioCoder.h"
|
||||
//#include "Debuger.h"
|
||||
|
||||
using namespace AAC_CODER;
|
||||
AACAudioCoder::~AACAudioCoder() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void AACAudioCoder::OnAudioData(const void *frameaddress, uint32_t framelen)
|
||||
{
|
||||
this->Encode((unsigned char *)frameaddress, framelen * 4);
|
||||
}
|
||||
|
||||
AACAudioCoder::AACAudioCoder(unsigned int smprate, unsigned int channel) {
|
||||
AVCodecID codec_id = AV_CODEC_ID_AAC;
|
||||
|
||||
pCodec = (AVCodec *)avcodec_find_encoder_by_name("libfdk_aac");
|
||||
if (!pCodec) {
|
||||
printf("Codec not found\n");
|
||||
this->mStatus = FAIL;
|
||||
}
|
||||
mCodecCtx = avcodec_alloc_context3(pCodec);
|
||||
if (!mCodecCtx) {
|
||||
printf("Could not allocate video codec context\n");
|
||||
this->mStatus = FAIL;
|
||||
}
|
||||
|
||||
mCodecCtx->codec_id = pCodec->id;
|
||||
mCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
mCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16; ///< float
|
||||
mCodecCtx->sample_rate = 44100;
|
||||
mCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
mCodecCtx->channels = 2;
|
||||
mCodecCtx->bit_rate = 640000;
|
||||
mCodecCtx->time_base.den = 1;
|
||||
mCodecCtx->time_base.num = 23;
|
||||
mCodecCtx->frame_size = 1024;
|
||||
this->mObserver = nullptr;
|
||||
|
||||
if (avcodec_open2(mCodecCtx, pCodec, NULL) < 0) {
|
||||
this->mStatus = FAIL;
|
||||
}
|
||||
mFrame = av_frame_alloc();
|
||||
mFrame->nb_samples = mCodecCtx->frame_size;
|
||||
mFrame->format = mCodecCtx->sample_fmt;
|
||||
int size = av_samples_get_buffer_size(NULL, mCodecCtx->channels, mCodecCtx->frame_size, mCodecCtx->sample_fmt, 1);
|
||||
mFrameBuf = (uint8_t *)av_malloc(size);
|
||||
avcodec_fill_audio_frame(mFrame, mCodecCtx->channels, mCodecCtx->sample_fmt, (const uint8_t*)mFrameBuf, size, 1);
|
||||
mPts = 0;
|
||||
}
|
||||
|
||||
int adts_sample_rates[]={96000,882000,64000,48000,441000,32000,24000,22050,16000,12000,11025,8000,7350,0,0,0};
|
||||
|
||||
int FindAdstSRIndex(int samplerate)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
if (samplerate == adts_sample_rates[i])
|
||||
return i;
|
||||
}
|
||||
return 16 - 1;
|
||||
}
|
||||
#define ADTS_HEAD_LEN 7
|
||||
|
||||
void MakeAdtsHeader(unsigned char *data, int samplerate, int channels, int iFrameLen)
|
||||
{
|
||||
int profile = 2; //AAC LC,MediaCodecInfo.CodecProfileLevel.AACObjectLC;
|
||||
int freqIdx = 4; //32K, 见后面注释avpriv_mpeg4audio_sample_rates中32000对应的数组下标,来自ffmpeg源码
|
||||
int chanCfg = channels; //见后面注释channel_configuration,Stero双声道立体声
|
||||
|
||||
/*int avpriv_mpeg4audio_sample_rates[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000,
|
||||
24000, 22050, 16000, 12000, 11025, 8000, 7350
|
||||
};
|
||||
channel_configuration: 表示声道数chanCfg
|
||||
0: Defined in AOT Specifc Config
|
||||
1: 1 channel: front-center
|
||||
2: 2 channels: front-left, front-right
|
||||
3: 3 channels: front-center, front-left, front-right
|
||||
4: 4 channels: front-center, front-left, front-right, back-center
|
||||
5: 5 channels: front-center, front-left, front-right, back-left, back-right
|
||||
6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
|
||||
7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
|
||||
8-15: Reserved
|
||||
*/
|
||||
|
||||
// fill in ADTS data
|
||||
data[0] = (uint8_t)0xFF;
|
||||
data[1] = (uint8_t)0xF9;
|
||||
data[2] = (uint8_t)(((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
|
||||
data[3] = (uint8_t)(((chanCfg & 3) << 6) + (iFrameLen >> 11));
|
||||
data[4] = (uint8_t)((iFrameLen & 0x7FF) >> 3);
|
||||
data[5] = (uint8_t)(((iFrameLen & 7) << 5) + 0x1F);
|
||||
data[6] = (uint8_t)0xFC;
|
||||
}
|
||||
|
||||
FILE *ptest = nullptr;
|
||||
int once = 1;
|
||||
int AACAudioCoder::Encode( unsigned char *input, unsigned int num) {
|
||||
|
||||
mFrame->nb_samples = mCodecCtx->frame_size;
|
||||
mFrame->format = mCodecCtx->sample_fmt;
|
||||
|
||||
avcodec_fill_audio_frame(mFrame, mCodecCtx->channels,
|
||||
mCodecCtx->sample_fmt, input,
|
||||
num, 1);
|
||||
|
||||
int aac_out_len = 0;
|
||||
unsigned char*aac_buf;
|
||||
if (nullptr == input) {
|
||||
return -1;
|
||||
}
|
||||
if (nullptr == ptest) {
|
||||
ptest = fopen("dst.aac", "wb");
|
||||
}
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = NULL; // packet data will be allocated by the encoder
|
||||
pkt.size = 0;
|
||||
|
||||
int got_output = 0;
|
||||
mFrame->pts = mPts += 23;
|
||||
int ret = avcodec_encode_audio2(mCodecCtx, &pkt, mFrame, &got_output);
|
||||
if (ret < 0) {
|
||||
printf("Error encoding frame\n");
|
||||
return -1;
|
||||
}
|
||||
if (got_output) {
|
||||
if (nullptr != mObserver) {
|
||||
mObserver->OnAudioEncode(pkt.data, pkt.size, mFrame->pts);
|
||||
}
|
||||
fwrite(pkt.data, 1, pkt.size, ptest);
|
||||
av_free_packet(&pkt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AAC_CODER::AACAudioCoder::SetObserver(EncodeAudioObserver *p)
|
||||
{
|
||||
if (nullptr == this->mObserver) {
|
||||
this->mObserver = p;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
#include "AudioCapture.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
};
|
||||
#endif
|
||||
namespace AAC_CODER {
|
||||
class AACAudioCoder :public CaptureAudio::CaptureAudioObserver {
|
||||
public:
|
||||
class EncodeAudioObserver {
|
||||
public:
|
||||
virtual void OnAudioEncode(const void *frameaddress, uint32_t framelen,uint16_t pts) {};
|
||||
};
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
READY = 4,
|
||||
UNREADY = 5,
|
||||
FAIL = 6,
|
||||
};
|
||||
void OnAudioData(const void *frameaddress, uint32_t framelen);
|
||||
AACAudioCoder(unsigned int smprate, unsigned int channel);
|
||||
~AACAudioCoder();
|
||||
int Encode(unsigned char *input, unsigned int num);
|
||||
int SetObserver(EncodeAudioObserver *);
|
||||
private:
|
||||
unsigned int mpts;
|
||||
CAP_STATUS mStatus;
|
||||
unsigned long mSampleRate = 44100;
|
||||
unsigned int mChannels = 2;
|
||||
unsigned int mPCMBitSize = 16;
|
||||
uint8_t* mAACBuffer;
|
||||
unsigned long nMaxOutputBytes;
|
||||
uintptr_t mFablaAacenc;
|
||||
AVCodec *mCodec;
|
||||
AVCodec *pCodec;
|
||||
AVCodecContext *mCodecCtx = NULL;
|
||||
AVFrame *mFrame;
|
||||
AVPacket pkt;
|
||||
uint8_t* mFrameBuf;
|
||||
uint16_t mPts;
|
||||
EncodeAudioObserver *mObserver;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
#include "AACDecoder.h"
|
||||
#include "Debuger.h"
|
||||
|
||||
|
||||
void AACDecoder::OnRtmpFrame(void * dat, uint32_t size)
|
||||
{
|
||||
this->Decode((uint8_t *)dat, size);
|
||||
}
|
||||
AACDecoder::AACDecoder() :mObserver(nullptr)
|
||||
{
|
||||
mStatus = RUNNING;
|
||||
this->mObserverType = Observer_Audio;
|
||||
mCodec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||
if (mCodec == NULL) {
|
||||
Debuger::Debug(L"find codec fail\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mCodecCtx = avcodec_alloc_context3(mCodec);
|
||||
if (nullptr == mCodecCtx) {
|
||||
Debuger::Debug(L"find codec ctx fail\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mCodecCtx->codec = mCodec;
|
||||
mCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
mCodecCtx->sample_rate = 44100;
|
||||
mCodecCtx->channels = 2;
|
||||
mCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
mCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
||||
mCodecCtx->frame_size = 2048;
|
||||
#if LIBSWRESAMPLE_VERSION_MINOR >= 17 // 根据版本不同,选用适当函数
|
||||
mSwrCtx = swr_alloc();
|
||||
|
||||
av_opt_set_int(mSwrCtx, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
|
||||
av_opt_set_int(mSwrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
|
||||
av_opt_set_int(mSwrCtx, "in_sample_rate", 44100, 0);
|
||||
av_opt_set_int(mSwrCtx, "out_sample_rate", 44100, 0);
|
||||
av_opt_set_sample_fmt(mSwrCtx, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
|
||||
av_opt_set_sample_fmt(mSwrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||
swr_init(mSwrCtx);
|
||||
|
||||
#else
|
||||
mSwrCtx = swr_alloc();
|
||||
|
||||
mSwrCtx = swr_alloc_set_opts(mSwrCtx,
|
||||
AV_CH_LAYOUT_STEREO, //output
|
||||
AV_SAMPLE_FMT_S16,
|
||||
44100,
|
||||
AV_CH_LAYOUT_STEREO, // input
|
||||
AV_SAMPLE_FMT_FLTP,
|
||||
44100,
|
||||
0, NULL);
|
||||
swr_init(mSwrCtx);
|
||||
#endif
|
||||
|
||||
if (avcodec_open2(mCodecCtx, mCodec, NULL) < 0) {
|
||||
Debuger::Debug(L"can't open codec\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mSampleRate = 44100;
|
||||
mChannel = 2;
|
||||
mChannelLayout = AV_CH_LAYOUT_STEREO;
|
||||
mSampleFmt = AV_SAMPLE_FMT_FLTP;
|
||||
mStatus = RUNNING;
|
||||
mU16Data = (uint8_t *)av_malloc(192000);
|
||||
}
|
||||
#define MAX_AUDIO_FRAME_SIZE 192000
|
||||
|
||||
AACDecoder::AACDecoder(AVStream * p):mObserver(nullptr)
|
||||
{
|
||||
mStatus = RUNNING;
|
||||
this->mObserverType = Observer_Audio;
|
||||
if (nullptr == p) {
|
||||
Debuger::Debug(L"find codec fail\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mCodecCtx = p->codec;
|
||||
mCodec = avcodec_find_decoder(mCodecCtx->codec_id);
|
||||
if (mCodec == NULL) {
|
||||
Debuger::Debug(L"find codec fail\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
if (avcodec_open2(mCodecCtx, mCodec, NULL) < 0) {
|
||||
Debuger::Debug(L"can't open codec\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mSampleRate = mCodecCtx->sample_rate;
|
||||
mChannel = mCodecCtx->channels;
|
||||
mChannelLayout = mCodecCtx->channel_layout;
|
||||
mSampleFmt = mCodecCtx->sample_fmt;
|
||||
}
|
||||
|
||||
int AACDecoder::Decode(uint8_t * dat, uint16_t size)
|
||||
{
|
||||
AVPacket pkt;
|
||||
int got_pcm = 0;
|
||||
int len = 0;
|
||||
|
||||
if (mStatus == RUNNING) {
|
||||
mPcmDat = av_frame_alloc();
|
||||
av_init_packet(&pkt);
|
||||
|
||||
char* data = (char*)dat;
|
||||
pkt.data = (uint8_t *)data;
|
||||
pkt.size = size;
|
||||
|
||||
len = avcodec_decode_audio4(this->mCodecCtx, mPcmDat, &got_pcm, &pkt);
|
||||
if (len < 0) {
|
||||
printf("Error while decoding a frame.\n");
|
||||
return -1;
|
||||
}
|
||||
if (got_pcm == 0) {
|
||||
return 0;
|
||||
}
|
||||
int buffer_size = av_samples_get_buffer_size(NULL, AV_CH_LAYOUT_STEREO,
|
||||
mPcmDat->nb_samples,
|
||||
AV_SAMPLE_FMT_S16, 1);
|
||||
|
||||
swr_convert(mSwrCtx, &mU16Data, buffer_size, (const uint8_t **)mPcmDat->data,
|
||||
mPcmDat->nb_samples);
|
||||
|
||||
//Debuger::Debug(L"get %d audio samples\r\n", mPcmDat->nb_samples);
|
||||
if (nullptr != this->mObserver) {
|
||||
int out_buffer_size = av_samples_get_buffer_size(NULL, 2, mPcmDat->nb_samples,
|
||||
AV_SAMPLE_FMT_FLTP, 1);
|
||||
this->mObserver->OnAudioDecode(mU16Data, buffer_size);
|
||||
}
|
||||
//(const uint8_t **)mPcmDat->data, mPcmDat->nb_samples;
|
||||
av_frame_free(&mPcmDat);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int AACDecoder::SetObserver(AACDecoderObserver *p)
|
||||
{
|
||||
if(nullptr != p)
|
||||
this->mObserver = p;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "RtmpPuller2.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libswresample\swresample.h"
|
||||
};
|
||||
|
||||
class AACDecoder :public RtmpPuller2::RtmpPullObserver {
|
||||
public:
|
||||
enum DECODE_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
NOSOURCE = 6,
|
||||
};
|
||||
class AACDecoderObserver {
|
||||
public:
|
||||
virtual int OnAudioDecode(uint8_t *dat, uint16_t size) { return 0; };
|
||||
};
|
||||
void OnRtmpFrame(void * dat, uint32_t size);
|
||||
AACDecoder();
|
||||
AACDecoder(AVStream *p);
|
||||
int Decode(uint8_t *dat,uint16_t);
|
||||
int SetObserver(AACDecoderObserver *);
|
||||
private:
|
||||
AVFormatContext *mFormatCtx = nullptr;
|
||||
AVCodecContext *mCodecCtx = nullptr;
|
||||
AVCodec *mCodec = nullptr;
|
||||
AVPacket *mPacket = nullptr;
|
||||
uint16_t mSampleCnt;
|
||||
enum AVSampleFormat mSampleFmt;
|
||||
uint16_t mSampleRate;
|
||||
uint16_t mChannel;
|
||||
uint64_t mChannelLayout;
|
||||
AVFrame *mPcmDat;
|
||||
uint8_t *mU16Data;
|
||||
AACDecoderObserver *mObserver;
|
||||
DECODE_STATUS mStatus;
|
||||
SwrContext * mSwrCtx;
|
||||
};
|
|
@ -0,0 +1,133 @@
|
|||
#include "AudioCapture.h"
|
||||
#include "Debuger.h"
|
||||
PaStream *gStreamOut = nullptr;
|
||||
|
||||
CaptureAudio::CaptureAudio(uint16_t rate, uint8_t channel) {
|
||||
this->mChanel = channel;
|
||||
this->mSampleRate = rate;
|
||||
this->mSize = 0;
|
||||
this->mStatus = FAIL;
|
||||
this->observer = nullptr;
|
||||
|
||||
}
|
||||
/**
|
||||
* @brief 设置捕获到的回调通知类
|
||||
*
|
||||
* @param CaptureAudioObserver 参数说明
|
||||
* @return 返回说明
|
||||
* @retval -1 输入为空指针
|
||||
* @retval 0成功
|
||||
*/
|
||||
int CaptureAudio::SetObserver(CaptureAudioObserver* ob) {
|
||||
if (nullptr == ob) return -1;
|
||||
this->observer = ob;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int paOutStreamBkss(const void* input, void* output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void * userData)
|
||||
{
|
||||
CaptureAudio *pCap;
|
||||
Debuger::Debug(L"%d\r\n", frameCount);
|
||||
if (userData != nullptr) {
|
||||
pCap = (CaptureAudio *)userData;
|
||||
pCap->OnCallBack(input,output,frameCount);
|
||||
}
|
||||
pCap->AddCnt(4 * frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CaptureAudio::OnCallBack(const void* input, void* output, unsigned long frameCount) {
|
||||
if(nullptr != this->observer)
|
||||
this->observer->OnAudioData(input, frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CaptureAudio::~CaptureAudio() {
|
||||
if(mInStream != nullptr)
|
||||
Pa_CloseStream(mInStream);
|
||||
}
|
||||
|
||||
int CaptureAudio::StartCapture()
|
||||
{
|
||||
PaError err = paNoError;
|
||||
if (this->mStatus == RUNNING) {
|
||||
err = Pa_StartStream(mInStream);
|
||||
if (err != paNoError) {
|
||||
this->mStatus = FAIL;
|
||||
}
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vector<CaptureAudio::MICInfo> CaptureAudio::EnumSpeakers()
|
||||
{
|
||||
vector<CaptureAudio::MICInfo> ret;
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
Debuger::Debug(L"init stream error\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
//获得设备数量
|
||||
PaDeviceIndex iNumDevices = Pa_GetDeviceCount();
|
||||
if (iNumDevices <= 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
for (int i = 0; i < iNumDevices; i++)
|
||||
{
|
||||
MICInfo ins;
|
||||
ins.index = i;
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (nullptr != deviceInfo)
|
||||
if (deviceInfo->maxInputChannels > 0) {
|
||||
ins.name = deviceInfo->name;
|
||||
ret.push_back(ins);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int CaptureAudio::InitCapture(int index,uint16_t rate, uint8_t channel) {
|
||||
PaStreamParameters intputParameters;
|
||||
PaError err = paNoError;
|
||||
err = Pa_Initialize();
|
||||
if (err != paNoError) goto error;
|
||||
if (index < 0)
|
||||
{
|
||||
index = Pa_GetDefaultInputDevice();
|
||||
}
|
||||
if (paNoDevice == index) {
|
||||
mStatus = FAIL;
|
||||
return -1;
|
||||
}
|
||||
intputParameters.device = index;
|
||||
intputParameters.channelCount = 2;
|
||||
intputParameters.sampleFormat = paInt16;
|
||||
intputParameters.suggestedLatency = Pa_GetDeviceInfo(intputParameters.device)->defaultLowInputLatency;
|
||||
intputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
err = Pa_OpenStream(&mInStream, &intputParameters, NULL, 44100, 1024,
|
||||
paFramesPerBufferUnspecified, paOutStreamBkss, this);
|
||||
if (err != paNoError) {
|
||||
this->mStatus = FAIL;
|
||||
return -1;
|
||||
}
|
||||
this->mStatus = RUNNING;
|
||||
return 0;
|
||||
error:
|
||||
Pa_Terminate();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CaptureAudio::StopCapture()
|
||||
{
|
||||
if (this->mStatus == RUNNING) {
|
||||
Pa_StopStream(mInStream);
|
||||
this->mStatus = STOP;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef __CAPTUREAUDIO_H__
|
||||
#define __CAPTUREAUDIO_H__
|
||||
#include "stdint.h"
|
||||
#include "../third/portaudio/portaudio.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
//Windows
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
|
||||
|
||||
};
|
||||
#include <functional>
|
||||
#include <dshow.h>
|
||||
#include <windows.h>
|
||||
#include "qedit.h"
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include "guiddef.h"
|
||||
using namespace std;
|
||||
|
||||
typedef int (CbAudio)(const void* input, void* output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void * userData);
|
||||
|
||||
class CaptureAudio {
|
||||
public:
|
||||
class CaptureAudioObserver {
|
||||
public:
|
||||
virtual void OnAudioData(const void *frameaddress, uint32_t framelen) {};
|
||||
};
|
||||
typedef struct MICInfo
|
||||
{
|
||||
string name;
|
||||
int index;
|
||||
}MICInfo;
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
};
|
||||
|
||||
vector<CaptureAudio::MICInfo> EnumSpeakers();
|
||||
CaptureAudio(uint16_t rate, uint8_t channel);
|
||||
~CaptureAudio();
|
||||
int StartCapture();
|
||||
int InitCapture(int index,uint16_t rate,uint8_t channel);
|
||||
void StopCapture();
|
||||
int SetObserver(CaptureAudioObserver*);
|
||||
int OnCallBack(const void* input, void* output, unsigned long frameCount);
|
||||
void AddCnt(unsigned int x) {this->mSize += x;};
|
||||
private:
|
||||
uint16_t mSampleRate; //²ÉÑùÂÊ
|
||||
uint16_t mChanel; //ͨµÀºÅ
|
||||
PaStream *mInStream;
|
||||
PaStream *mOutStream;
|
||||
unsigned long mSize;
|
||||
CAP_STATUS mStatus;
|
||||
CaptureAudioObserver *observer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //__CAPTUREAUDIO_H__
|
|
@ -0,0 +1,91 @@
|
|||
#include "AudioPlayer.h"
|
||||
#include "Debuger.h"
|
||||
#include "utils.h"
|
||||
AudioPlayer::AudioPlayer(int index)
|
||||
{
|
||||
mStatus = RUNNING;
|
||||
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
Debuger::Debug(L"init stream error\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
//获得设备数量
|
||||
PaDeviceIndex iNumDevices = Pa_GetDeviceCount();
|
||||
if (iNumDevices < 0)
|
||||
{
|
||||
}
|
||||
for (int i = 0; i < iNumDevices; i++)
|
||||
{
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
Debuger::Debug(L"index %d %d %d \r\n",i,
|
||||
deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels); //打印设备名
|
||||
}
|
||||
mOutputParameters.device = index;
|
||||
mOutputParameters.channelCount = 2; //输出采用双声道,左声道在前
|
||||
mOutputParameters.sampleFormat = paInt16;
|
||||
mOutputParameters.suggestedLatency = Pa_GetDeviceInfo(mOutputParameters.device)->defaultLowOutputLatency;
|
||||
mOutputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
err = Pa_OpenStream(&mOutStream, NULL, &mOutputParameters, 44100, 1024,
|
||||
paFramesPerBufferUnspecified, NULL, NULL);
|
||||
if (err != paNoError) {
|
||||
Debuger::Debug(L"open output stream error\r\n");
|
||||
mStatus = FAIL;
|
||||
goto end;
|
||||
}
|
||||
err = Pa_StartStream(mOutStream);
|
||||
if (err != paNoError) {
|
||||
Debuger::Debug(L"start stream error\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
end:
|
||||
return;
|
||||
}
|
||||
|
||||
vector<AudioPlayer::SpeakerInfo> AudioPlayer::EnumSpeakers()
|
||||
{
|
||||
vector<AudioPlayer::SpeakerInfo> ret;
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
Debuger::Debug(L"init stream error\r\n");
|
||||
mStatus = FAIL;
|
||||
}
|
||||
//获得设备数量
|
||||
PaDeviceIndex iNumDevices = Pa_GetDeviceCount();
|
||||
if (iNumDevices <= 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
for (int i = 0; i < iNumDevices; i++)
|
||||
{
|
||||
SpeakerInfo ins;
|
||||
ins.index = i;
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if(nullptr != deviceInfo)
|
||||
if (deviceInfo->maxOutputChannels > 0) {
|
||||
//ins.name = char2wchar(deviceInfo->name);
|
||||
ret.push_back(ins);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int AudioPlayer::Play(uint8_t * data, uint16_t num)
|
||||
{
|
||||
PaError err;
|
||||
if (mStatus == RUNNING) {
|
||||
err = Pa_WriteStream(mOutStream, data, num);
|
||||
if (paNoError != err) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int AudioPlayer::OnAudioDecode(uint8_t * dat, uint16_t size)
|
||||
{
|
||||
return this->Play(dat, 1024);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include "stdint.h"
|
||||
#include "../third/portaudio/portaudio.h"
|
||||
#include "AACDecoder.h"
|
||||
|
||||
class AudioPlayer :public AACDecoder::AACDecoderObserver{
|
||||
public:
|
||||
class AudioPlayerObserver{
|
||||
public:
|
||||
virtual int OnAudioPlay();
|
||||
};
|
||||
typedef struct {
|
||||
int index;
|
||||
wstring name;
|
||||
}SpeakerInfo;
|
||||
enum PLAY_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
};
|
||||
AudioPlayer(int index);
|
||||
vector<SpeakerInfo> EnumSpeakers();
|
||||
int Play(uint8_t *data,uint16_t num);
|
||||
int OnAudioDecode(uint8_t *dat, uint16_t size);
|
||||
private:
|
||||
PLAY_STATUS mStatus;
|
||||
PaStreamParameters mOutputParameters;
|
||||
PaStream *mOutStream;
|
||||
|
||||
};
|
|
@ -0,0 +1,431 @@
|
|||
#include "CameraCapture.h"
|
||||
#include<iostream>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#pragma comment(lib, "strmiids")
|
||||
#endif
|
||||
//define release maco
|
||||
#define ReleaseInterface(x) \
|
||||
if ( nullptr != x ) \
|
||||
{ \
|
||||
x->Release( ); \
|
||||
x = nullptr; \
|
||||
}
|
||||
// Application-defined message to notify app of filter graph events
|
||||
#define WM_GRAPHNOTIFY WM_APP+100
|
||||
|
||||
Camera::Camera():
|
||||
mInitOK(false),
|
||||
mVideoHeight(0),
|
||||
mVideoWidth(0),
|
||||
mDevFilter(nullptr),
|
||||
mCaptureGB(nullptr),
|
||||
mGraphBuilder(nullptr),
|
||||
mMediaControl(nullptr),
|
||||
mMediaEvent(nullptr),
|
||||
mSampGrabber(nullptr),
|
||||
mIsVideoOpened(false),
|
||||
mDebug(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
GUID Camera::MediaType()
|
||||
{
|
||||
return mMediaType;
|
||||
}
|
||||
|
||||
Camera::Camera(wstring camera)
|
||||
{
|
||||
mInitOK = false;
|
||||
mVideoHeight = 0;
|
||||
mVideoWidth = 0;
|
||||
mDevFilter = nullptr;
|
||||
mCaptureGB = nullptr;
|
||||
mGraphBuilder = nullptr;
|
||||
mMediaControl = nullptr;
|
||||
mMediaEvent = nullptr;
|
||||
mSampGrabber = nullptr;
|
||||
mIsVideoOpened = false;
|
||||
if(!this->Open(camera)){
|
||||
mStatus = FAIL;
|
||||
}
|
||||
mStatus = STOP;
|
||||
}
|
||||
|
||||
Camera::~Camera()
|
||||
{
|
||||
Close();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
HRESULT Camera::InitializeEnv() {
|
||||
HRESULT hr;
|
||||
|
||||
//Create the filter graph
|
||||
hr = CoCreateInstance(CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_IGraphBuilder, (LPVOID*)&mGraphBuilder);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
//Create the capture graph builder
|
||||
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_ICaptureGraphBuilder2, (LPVOID*)&mCaptureGB);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
//Obtain interfaces for media control and Video Window
|
||||
hr = mGraphBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&mMediaControl);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
|
||||
hr = mGraphBuilder->QueryInterface(IID_IMediaEventEx, (LPVOID*)&mMediaEvent);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
mCaptureGB->SetFiltergraph(mGraphBuilder);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
return hr;
|
||||
}
|
||||
|
||||
std::vector<std::wstring> Camera::EnumAllCamera(void) {
|
||||
|
||||
std::vector<std::wstring> names;
|
||||
IEnumMoniker *pEnum = nullptr;
|
||||
// Create the System Device Enumerator.
|
||||
ICreateDevEnum *pDevEnum;
|
||||
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
|
||||
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// Create an enumerator for the category.
|
||||
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
|
||||
if (hr == S_FALSE)
|
||||
{
|
||||
hr = VFW_E_NOT_FOUND; // The category is empty. Treat as an error.
|
||||
}
|
||||
pDevEnum->Release();
|
||||
}
|
||||
|
||||
if (!SUCCEEDED(hr))
|
||||
return std::vector<std::wstring>();
|
||||
|
||||
IMoniker *pMoniker = nullptr;
|
||||
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK)
|
||||
{
|
||||
IPropertyBag *pPropBag;
|
||||
IBindCtx* bindCtx = nullptr;
|
||||
LPOLESTR str = nullptr;
|
||||
VARIANT var;
|
||||
VariantInit(&var);
|
||||
|
||||
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
pMoniker->Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get description or friendly name.
|
||||
hr = pPropBag->Read(L"Description", &var, 0);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
hr = pPropBag->Read(L"FriendlyName", &var, 0);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
names.push_back(var.bstrVal);
|
||||
VariantClear(&var);
|
||||
}
|
||||
|
||||
pPropBag->Release();
|
||||
pMoniker->Release();
|
||||
}
|
||||
|
||||
pEnum->Release();
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
HRESULT Camera::BindFilter(int deviceID, IBaseFilter **pBaseFilter) {
|
||||
ICreateDevEnum *pDevEnum;
|
||||
IEnumMoniker *pEnumMon;
|
||||
IMoniker *pMoniker;
|
||||
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_ICreateDevEnum, (LPVOID*)&pDevEnum);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumMon, 0);
|
||||
if (hr == S_FALSE)
|
||||
{
|
||||
hr = VFW_E_NOT_FOUND;
|
||||
return hr;
|
||||
}
|
||||
pEnumMon->Reset();
|
||||
ULONG cFetched;
|
||||
int index = 0;
|
||||
hr = pEnumMon->Next(1, &pMoniker, &cFetched);
|
||||
while (hr == S_OK && index <= deviceID) {
|
||||
IPropertyBag *pProBag;
|
||||
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (LPVOID*)&pProBag);
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (index == deviceID) {
|
||||
pMoniker->BindToObject(0, 0, IID_IBaseFilter, (LPVOID*)pBaseFilter);
|
||||
}
|
||||
}
|
||||
pMoniker->Release();
|
||||
index++;
|
||||
hr = pEnumMon->Next(1, &pMoniker, &cFetched);
|
||||
}
|
||||
pEnumMon->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
int Camera::SetObserver(CameraObserver *p) {
|
||||
return this->mSampleGrabberCB.SetObserver(p);
|
||||
}
|
||||
|
||||
int Camera::RemoveObserver(CameraObserver * p) {
|
||||
return this->mSampleGrabberCB.RemoveObserver(p);
|
||||
}
|
||||
|
||||
void Camera::SetDebug(bool isDebug) {
|
||||
mDebug = isDebug;
|
||||
}
|
||||
|
||||
int Camera::SampleGrabberCallback::SetObserver(CameraObserver *p) {
|
||||
if (nullptr == p)
|
||||
return -1;
|
||||
mMux.lock();
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
if (p == *itr) {
|
||||
mMux.unlock();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
this->mObserver.push_back(p);
|
||||
mMux.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Camera::SampleGrabberCallback::RemoveObserver(CameraObserver * p)
|
||||
{
|
||||
mMux.lock();
|
||||
bool founded = false;
|
||||
auto itrDel = this->mObserver.begin();
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
if (p == *itr) {
|
||||
itrDel = itr;
|
||||
founded = true;
|
||||
}
|
||||
}
|
||||
if (founded)
|
||||
mObserver.erase(itrDel);
|
||||
mMux.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool Camera::Open(std::wstring &camera_name)
|
||||
{
|
||||
if (mIsVideoOpened)
|
||||
return true;
|
||||
HRESULT hr;
|
||||
#define CHECK_HR(x) do{ hr = (x); if (FAILED(hr)){ Close(); return false;}}while(0)
|
||||
|
||||
CHECK_HR(InitializeEnv());
|
||||
|
||||
IBaseFilter *pSampleGrabberFilter , *dest_filter;
|
||||
|
||||
std::vector<std::wstring> names = EnumAllCamera();
|
||||
|
||||
if (names.empty())
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
bool founded = false;
|
||||
int deviceID = 0;
|
||||
|
||||
for(std::wstring i : names ){
|
||||
if(i == camera_name){
|
||||
founded = true;
|
||||
}
|
||||
}
|
||||
if (!founded){
|
||||
return false;
|
||||
}
|
||||
// create grabber filter instance
|
||||
CHECK_HR(CoCreateInstance(CLSID_SampleGrabber, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_IBaseFilter, (LPVOID*)&pSampleGrabberFilter));
|
||||
|
||||
// bind source device
|
||||
CHECK_HR(BindFilter(deviceID, &mDevFilter));
|
||||
|
||||
// add src filter
|
||||
CHECK_HR(mGraphBuilder->AddFilter(mDevFilter, L"Video Filter"));
|
||||
|
||||
|
||||
// add grabber filter and query interface
|
||||
CHECK_HR(mGraphBuilder->AddFilter(pSampleGrabberFilter, L"Sample Grabber"));
|
||||
|
||||
CHECK_HR(pSampleGrabberFilter->QueryInterface(IID_ISampleGrabber, (LPVOID*)&mSampGrabber));
|
||||
|
||||
// find the current bit depth
|
||||
HDC hdc = GetDC(nullptr);
|
||||
mBitDepth = GetDeviceCaps(hdc, BITSPIXEL);
|
||||
ReleaseDC(nullptr, hdc);
|
||||
|
||||
// set the media type for grabber filter
|
||||
AM_MEDIA_TYPE mediaType;
|
||||
ZeroMemory(&mediaType, sizeof(AM_MEDIA_TYPE));
|
||||
mediaType.majortype = MEDIATYPE_Video;
|
||||
switch (mBitDepth)
|
||||
{
|
||||
case 8:
|
||||
mediaType.subtype = MEDIASUBTYPE_RGB8;
|
||||
break;
|
||||
case 16:
|
||||
mediaType.subtype = MEDIASUBTYPE_RGB555;
|
||||
break;
|
||||
case 24:
|
||||
mediaType.subtype = MEDIASUBTYPE_RGB24;
|
||||
break;
|
||||
case 32:
|
||||
mediaType.subtype = MEDIASUBTYPE_RGB32;
|
||||
break;
|
||||
default:
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
mediaType.formattype = FORMAT_VideoInfo;
|
||||
hr = mSampGrabber->SetMediaType(&mediaType);
|
||||
// 意味着最后的数据是丢掉的
|
||||
CHECK_HR(CoCreateInstance(CLSID_NullRenderer, nullptr, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)(&dest_filter)));
|
||||
mGraphBuilder->AddFilter(dest_filter, L"nullptrRenderer");
|
||||
|
||||
// connect source filter to grabber filter
|
||||
CHECK_HR(mCaptureGB->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
|
||||
mDevFilter, pSampleGrabberFilter, dest_filter));
|
||||
|
||||
// get connected media type
|
||||
CHECK_HR(mSampGrabber->GetConnectedMediaType(&mediaType));
|
||||
VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*)mediaType.pbFormat;
|
||||
mVideoWidth = vih->bmiHeader.biWidth;
|
||||
mVideoHeight = vih->bmiHeader.biHeight;
|
||||
mPixFmt = mediaType.subtype;
|
||||
mMediaType = mediaType.subtype;
|
||||
std::cout<<"guid media type is"<<mediaType.subtype.Data1<<" "<<
|
||||
mediaType.subtype.Data2<<" "<<
|
||||
mediaType.subtype.Data3<<" "<<
|
||||
mediaType.subtype.Data4<<" "<<
|
||||
mVideoWidth<<" "<<mVideoHeight;
|
||||
// configure grabber filter
|
||||
CHECK_HR(mSampGrabber->SetOneShot(0));
|
||||
|
||||
CHECK_HR(mSampGrabber->SetBufferSamples(0));
|
||||
|
||||
// Use the BufferCB callback method
|
||||
CHECK_HR(mSampGrabber->SetCallback(&mSampleGrabberCB, 1));
|
||||
|
||||
mSampleGrabberCB.mNewDataCallBack = mFrameCallBack;
|
||||
|
||||
mMediaControl->Run();
|
||||
dest_filter->Release();
|
||||
pSampleGrabberFilter->Release();
|
||||
|
||||
// release resource
|
||||
if (mediaType.cbFormat != 0)
|
||||
{
|
||||
CoTaskMemFree((PVOID)mediaType.pbFormat);
|
||||
mediaType.cbFormat = 0;
|
||||
mediaType.pbFormat = nullptr;
|
||||
}
|
||||
if (mediaType.pUnk != nullptr)
|
||||
{
|
||||
mediaType.pUnk->Release();
|
||||
mediaType.pUnk = nullptr;
|
||||
}
|
||||
mIsVideoOpened = TRUE;
|
||||
mStatus = RUNNING;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Camera::Close() {
|
||||
if (mMediaControl)
|
||||
{
|
||||
mMediaControl->Stop();
|
||||
}
|
||||
if (mMediaEvent)
|
||||
{
|
||||
mMediaEvent->SetNotifyWindow(NULL, WM_GRAPHNOTIFY, 0);
|
||||
}
|
||||
mIsVideoOpened = false;
|
||||
//release interface
|
||||
ReleaseInterface(mDevFilter);
|
||||
ReleaseInterface(mCaptureGB);
|
||||
ReleaseInterface(mGraphBuilder);
|
||||
ReleaseInterface(mMediaControl);
|
||||
ReleaseInterface(mMediaEvent);
|
||||
ReleaseInterface(mSampGrabber);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Camera::SetCallBack(std::function<void(double, BYTE*, LONG)> f) {
|
||||
mFrameCallBack = f;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Camera::SampleGrabberCallback::AddRef() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Camera::SampleGrabberCallback::Release() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Camera::SampleGrabberCallback::QueryInterface(REFIID riid, void** ppvObject) {
|
||||
if (nullptr == ppvObject) return E_POINTER;
|
||||
if (riid == __uuidof(IUnknown))
|
||||
{
|
||||
*ppvObject = static_cast<IUnknown*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
if (riid == IID_ISampleGrabberCB)
|
||||
{
|
||||
*ppvObject = static_cast<ISampleGrabberCB*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Camera::SampleGrabberCallback::SampleCB(double Time, IMediaSample *pSample) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Camera::SampleGrabberCallback::BufferCB(double Time, BYTE * pBuffer, long BufferLen)
|
||||
{
|
||||
#ifdef DEBUG_CAMERA
|
||||
static FILE *p = fopen("camera_test.yuv","wb+");
|
||||
fwrite(pBuffer,BufferLen,1,p);
|
||||
fflush(p);
|
||||
#endif
|
||||
|
||||
|
||||
if (mObserver.size() > 0) {
|
||||
mMux.lock();
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
CameraObserver *p = (CameraObserver *)*itr;
|
||||
p->OnCameraData(pBuffer, BufferLen);
|
||||
}
|
||||
mMux.unlock();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <dshow.h>
|
||||
#include <windows.h>
|
||||
#include "qedit.h"
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "guiddef.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
};
|
||||
class CameraObserver {
|
||||
public:
|
||||
virtual int OnCameraData(uint8_t *dat, uint32_t size) { return 0; };
|
||||
};
|
||||
|
||||
|
||||
class SampleGrabberCallback : public ISampleGrabberCB
|
||||
{
|
||||
public:
|
||||
ULONG STDMETHODCALLTYPE AddRef();
|
||||
ULONG STDMETHODCALLTYPE Release();
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);
|
||||
HRESULT STDMETHODCALLTYPE SampleCB(double Time, IMediaSample *pSample);
|
||||
HRESULT STDMETHODCALLTYPE BufferCB(double Time, BYTE *pBuffer, long BufferLen);
|
||||
std::function<void(double, BYTE *, LONG)> mNewDataCallBack;
|
||||
mutex mMux;
|
||||
|
||||
int SetObserver(CameraObserver *);
|
||||
int RemoveObserver(CameraObserver *p);
|
||||
private:
|
||||
vector<CameraObserver*> mObserver;
|
||||
};
|
||||
|
||||
|
||||
Camera(wstring camera);
|
||||
Camera(const Camera &) = delete;
|
||||
Camera& operator =(const Camera&) = delete;
|
||||
~Camera();
|
||||
|
||||
private:
|
||||
Camera();
|
||||
|
||||
bool mInitOK;
|
||||
bool mIsVideoOpened;
|
||||
|
||||
int mVideoWidth, mVideoHeight, mBitDepth;
|
||||
std::function<void(double, BYTE *, LONG)> mFrameCallBack;
|
||||
|
||||
IGraphBuilder *mGraphBuilder;
|
||||
ICaptureGraphBuilder2 *mCaptureGB;
|
||||
IMediaControl *mMediaControl;
|
||||
IBaseFilter *mDevFilter;
|
||||
ISampleGrabber *mSampGrabber;
|
||||
IMediaEventEx *mMediaEvent;
|
||||
|
||||
SampleGrabberCallback mSampleGrabberCB;
|
||||
HRESULT InitializeEnv();
|
||||
HRESULT BindFilter(int deviceID, IBaseFilter **pBaseFilter);
|
||||
GUID mMediaType;
|
||||
bool mDebug;
|
||||
public:
|
||||
int SetObserver(CameraObserver *);
|
||||
int RemoveObserver(CameraObserver *p);
|
||||
CAP_STATUS mStatus;
|
||||
|
||||
void SetDebug(bool);
|
||||
static std::vector<std::wstring> EnumAllCamera(void);
|
||||
GUID mPixFmt;
|
||||
bool Open(std::wstring &camera_name);
|
||||
bool Close(void);
|
||||
/*!
|
||||
* @param time : Starting time of the sample, in seconds.
|
||||
* @param buff : Pointer to a buffer that contains the sample data.
|
||||
* @param len : Length of the buffer pointed to by pBuffer, in bytes.
|
||||
*/
|
||||
void SetCallBack(std::function<void(double time, BYTE *buff, LONG len)>);
|
||||
int GetHeight() { return mVideoHeight; }
|
||||
int GetWidth() { return mVideoWidth; }
|
||||
int GetBitDepth() { return mBitDepth; }
|
||||
GUID MediaType();
|
||||
|
||||
|
||||
};
|
|
@ -0,0 +1,684 @@
|
|||
/*****************************************************************************
|
||||
* DXGICapture.cpp
|
||||
*
|
||||
* Copyright (C) 2020 Gokhan Erdogdu <gokhan_erdogdu - at - yahoo - dot - com>
|
||||
*
|
||||
* DXGICapture is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* DXGICapture is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
******************************************************************************/
|
||||
#include "DXGICapture.h"
|
||||
#include "DXGICaptureHelper.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#pragma comment(lib, "D3D11.lib")
|
||||
#pragma comment(lib, "d2d1.lib")
|
||||
#pragma comment(lib, "windowscodecs.lib")
|
||||
|
||||
#pragma comment(lib, "shcore.lib") // SetProcessDpiAwareness
|
||||
|
||||
// Driver types supported
|
||||
const D3D_DRIVER_TYPE g_DriverTypes[] =
|
||||
{
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
D3D_DRIVER_TYPE_WARP,
|
||||
D3D_DRIVER_TYPE_REFERENCE,
|
||||
};
|
||||
const UINT g_NumDriverTypes = ARRAYSIZE(g_DriverTypes);
|
||||
|
||||
// Feature levels supported
|
||||
const D3D_FEATURE_LEVEL g_FeatureLevels[] =
|
||||
{
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
const UINT g_NumFeatureLevels = ARRAYSIZE(g_FeatureLevels);
|
||||
|
||||
#define AUTOLOCK() ATL::CComCritSecLock<ATL::CComAutoCriticalSection> auto_lock((ATL::CComAutoCriticalSection&)(m_csLock))
|
||||
|
||||
//
|
||||
// class CDXGICapture
|
||||
//
|
||||
CDXGICapture::CDXGICapture()
|
||||
: m_csLock()
|
||||
, m_bInitialized(FALSE)
|
||||
, m_lD3DFeatureLevel(D3D_FEATURE_LEVEL_INVALID)
|
||||
{
|
||||
RtlZeroMemory(&m_rendererInfo, sizeof(m_rendererInfo));
|
||||
RtlZeroMemory(&m_mouseInfo, sizeof(m_mouseInfo));
|
||||
RtlZeroMemory(&m_tempMouseBuffer, sizeof(m_tempMouseBuffer));
|
||||
RtlZeroMemory(&m_desktopOutputDesc, sizeof(m_desktopOutputDesc));
|
||||
}
|
||||
|
||||
CDXGICapture::~CDXGICapture()
|
||||
{
|
||||
this->Terminate();
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::loadMonitorInfos(ID3D11Device *pDevice)
|
||||
{
|
||||
CHECK_POINTER(pDevice);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
CComPtr<ID3D11Device> ipDevice(pDevice);
|
||||
|
||||
// Get DXGI device
|
||||
CComPtr<IDXGIDevice> ipDxgiDevice;
|
||||
hr = ipDevice->QueryInterface(IID_PPV_ARGS(&ipDxgiDevice));
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Get DXGI adapter
|
||||
CComPtr<IDXGIAdapter> ipDxgiAdapter;
|
||||
hr = ipDxgiDevice->GetParent(IID_PPV_ARGS(&ipDxgiAdapter));
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
ipDxgiDevice = nullptr;
|
||||
|
||||
CComPtr<IDXGIOutput> ipDxgiOutput;
|
||||
for (UINT i = 0; SUCCEEDED(hr); ++i)
|
||||
{
|
||||
ipDxgiOutput = nullptr;
|
||||
hr = ipDxgiAdapter->EnumOutputs(i, &ipDxgiOutput);
|
||||
if ((nullptr != ipDxgiOutput) && (hr != DXGI_ERROR_NOT_FOUND))
|
||||
{
|
||||
DXGI_OUTPUT_DESC DesktopDesc;
|
||||
hr = ipDxgiOutput->GetDesc(&DesktopDesc);
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tagDublicatorMonitorInfo *pInfo;
|
||||
pInfo = new (std::nothrow) tagDublicatorMonitorInfo;
|
||||
if (nullptr == pInfo) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
hr = DXGICaptureHelper::ConvertDxgiOutputToMonitorInfo(&DesktopDesc, i, pInfo);
|
||||
if (FAILED(hr)) {
|
||||
delete pInfo;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_monitorInfos.push_back(pInfo);
|
||||
}
|
||||
}
|
||||
|
||||
ipDxgiOutput = nullptr;
|
||||
ipDxgiAdapter = nullptr;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void CDXGICapture::freeMonitorInfos()
|
||||
{
|
||||
size_t nCount = m_monitorInfos.size();
|
||||
if (nCount == 0) {
|
||||
return;
|
||||
}
|
||||
DublicatorMonitorInfoVec::iterator it = m_monitorInfos.begin();
|
||||
DublicatorMonitorInfoVec::iterator end = m_monitorInfos.end();
|
||||
for (size_t i = 0; (i < nCount) && (it != end); i++, it++) {
|
||||
tagDublicatorMonitorInfo *pInfo = *it;
|
||||
if (nullptr != pInfo) {
|
||||
delete pInfo;
|
||||
}
|
||||
}
|
||||
m_monitorInfos.clear();
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::createDeviceResource(
|
||||
const tagScreenCaptureFilterConfig *pConfig,
|
||||
const tagDublicatorMonitorInfo *pSelectedMonitorInfo
|
||||
)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
CComPtr<IDXGIOutputDuplication> ipDxgiOutputDuplication;
|
||||
CComPtr<ID3D11Texture2D> ipCopyTexture2D;
|
||||
CComPtr<ID2D1Device> ipD2D1Device;
|
||||
CComPtr<ID2D1DeviceContext> ipD2D1DeviceContext;
|
||||
CComPtr<ID2D1Factory> ipD2D1Factory;
|
||||
CComPtr<IWICImagingFactory> ipWICImageFactory;
|
||||
CComPtr<IWICBitmap> ipWICOutputBitmap;
|
||||
CComPtr<ID2D1RenderTarget> ipD2D1RenderTarget;
|
||||
DXGI_OUTPUT_DESC dgixOutputDesc;
|
||||
tagRendererInfo rendererInfo;
|
||||
|
||||
RtlZeroMemory(&dgixOutputDesc, sizeof(dgixOutputDesc));
|
||||
RtlZeroMemory(&rendererInfo, sizeof(rendererInfo));
|
||||
|
||||
// copy configuration to renderer info
|
||||
rendererInfo.MonitorIdx = pConfig->MonitorIdx;
|
||||
rendererInfo.ShowCursor = pConfig->ShowCursor;
|
||||
rendererInfo.RotationMode = pConfig->RotationMode;
|
||||
rendererInfo.SizeMode = pConfig->SizeMode;
|
||||
rendererInfo.OutputSize = pConfig->OutputSize;
|
||||
// default
|
||||
rendererInfo.ScaleX = 1.0f;
|
||||
rendererInfo.ScaleY = 1.0f;
|
||||
|
||||
do
|
||||
{
|
||||
// Get DXGI factory
|
||||
CComPtr<IDXGIDevice> ipDxgiDevice;
|
||||
hr = m_ipD3D11Device->QueryInterface(IID_PPV_ARGS(&ipDxgiDevice));
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
CComPtr<IDXGIAdapter> ipDxgiAdapter;
|
||||
hr = ipDxgiDevice->GetParent(IID_PPV_ARGS(&ipDxgiAdapter));
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// Get output
|
||||
CComPtr<IDXGIOutput> ipDxgiOutput;
|
||||
hr = ipDxgiAdapter->EnumOutputs(rendererInfo.MonitorIdx, &ipDxgiOutput);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// Get output description
|
||||
hr = ipDxgiOutput->GetDesc(&dgixOutputDesc);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
tagDublicatorMonitorInfo curMonInfo;
|
||||
hr = DXGICaptureHelper::ConvertDxgiOutputToMonitorInfo(&dgixOutputDesc, rendererInfo.MonitorIdx, &curMonInfo);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
if (!DXGICaptureHelper::IsEqualMonitorInfo(pSelectedMonitorInfo, &curMonInfo)) {
|
||||
hr = E_INVALIDARG; // Monitor settings have changed ???
|
||||
break;
|
||||
}
|
||||
|
||||
// QI for Output 1
|
||||
CComPtr<IDXGIOutput1> ipDxgiOutput1;
|
||||
hr = ipDxgiOutput->QueryInterface(IID_PPV_ARGS(&ipDxgiOutput1));
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// Create desktop duplication
|
||||
hr = ipDxgiOutput1->DuplicateOutput(m_ipD3D11Device, &ipDxgiOutputDuplication);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
DXGI_OUTDUPL_DESC dxgiOutputDuplDesc;
|
||||
ipDxgiOutputDuplication->GetDesc(&dxgiOutputDuplDesc);
|
||||
|
||||
hr = DXGICaptureHelper::CalculateRendererInfo(&dxgiOutputDuplDesc, &rendererInfo);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// Create CPU access texture
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
desc.Width = rendererInfo.SrcBounds.Width;
|
||||
desc.Height = rendererInfo.SrcBounds.Height;
|
||||
desc.Format = rendererInfo.SrcFormat;
|
||||
desc.ArraySize = 1;
|
||||
desc.BindFlags = 0;
|
||||
desc.MiscFlags = 0;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.MipLevels = 1;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
|
||||
hr = m_ipD3D11Device->CreateTexture2D(&desc, NULL, &ipCopyTexture2D);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
if (nullptr == ipCopyTexture2D)
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma region <For_2D_operations>
|
||||
|
||||
// Create D2D1 device
|
||||
UINT uiFlags = m_ipD3D11Device->GetCreationFlags();
|
||||
D2D1_CREATION_PROPERTIES d2d1Props = D2D1::CreationProperties
|
||||
(
|
||||
(uiFlags & D3D11_CREATE_DEVICE_SINGLETHREADED)
|
||||
? D2D1_THREADING_MODE_SINGLE_THREADED
|
||||
: D2D1_THREADING_MODE_MULTI_THREADED,
|
||||
D2D1_DEBUG_LEVEL_NONE,
|
||||
(uiFlags & D3D11_CREATE_DEVICE_SINGLETHREADED)
|
||||
? D2D1_DEVICE_CONTEXT_OPTIONS_NONE
|
||||
: D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS
|
||||
);
|
||||
hr = D2D1CreateDevice(ipDxgiDevice, d2d1Props, &ipD2D1Device);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// Get D2D1 factory
|
||||
ipD2D1Device->GetFactory(&ipD2D1Factory);
|
||||
|
||||
if (nullptr == ipD2D1Factory)
|
||||
{
|
||||
hr = D2DERR_INVALID_CALL;
|
||||
break;
|
||||
}
|
||||
|
||||
//create WIC factory
|
||||
hr = CoCreateInstance(
|
||||
CLSID_WICImagingFactory,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IWICImagingFactory,
|
||||
reinterpret_cast<void **>(&ipWICImageFactory)
|
||||
);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
// create D2D1 target bitmap for render
|
||||
hr = ipWICImageFactory->CreateBitmap(
|
||||
(UINT)rendererInfo.OutputSize.Width,
|
||||
(UINT)rendererInfo.OutputSize.Height,
|
||||
GUID_WICPixelFormat32bppPBGRA,
|
||||
WICBitmapCacheOnDemand,
|
||||
&ipWICOutputBitmap);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
if (nullptr == ipWICOutputBitmap)
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
// create a D2D1 render target (for D2D1 drawing)
|
||||
D2D1_RENDER_TARGET_PROPERTIES d2d1RenderTargetProp = D2D1::RenderTargetProperties
|
||||
(
|
||||
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
|
||||
0.0f, // default dpi
|
||||
0.0f, // default dpi
|
||||
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE
|
||||
);
|
||||
hr = ipD2D1Factory->CreateWicBitmapRenderTarget(
|
||||
ipWICOutputBitmap,
|
||||
d2d1RenderTargetProp,
|
||||
&ipD2D1RenderTarget
|
||||
);
|
||||
CHECK_HR_BREAK(hr);
|
||||
|
||||
#pragma endregion </For_2D_operations>
|
||||
|
||||
} while (false);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// copy output parameters
|
||||
memcpy_s((void*)&m_rendererInfo, sizeof(m_rendererInfo), (const void*)&rendererInfo, sizeof(m_rendererInfo));
|
||||
|
||||
// set parameters
|
||||
m_desktopOutputDesc = dgixOutputDesc;
|
||||
|
||||
m_ipDxgiOutputDuplication = ipDxgiOutputDuplication;
|
||||
m_ipCopyTexture2D = ipCopyTexture2D;
|
||||
|
||||
m_ipD2D1Device = ipD2D1Device;
|
||||
m_ipD2D1Factory = ipD2D1Factory;
|
||||
m_ipWICImageFactory = ipWICImageFactory;
|
||||
m_ipWICOutputBitmap = ipWICOutputBitmap;
|
||||
m_ipD2D1RenderTarget = ipD2D1RenderTarget;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void CDXGICapture::terminateDeviceResource()
|
||||
{
|
||||
m_ipDxgiOutputDuplication = nullptr;
|
||||
m_ipCopyTexture2D = nullptr;
|
||||
|
||||
m_ipD2D1Device = nullptr;
|
||||
m_ipD2D1Factory = nullptr;
|
||||
m_ipWICImageFactory = nullptr;
|
||||
m_ipWICOutputBitmap = nullptr;
|
||||
m_ipD2D1RenderTarget = nullptr;
|
||||
|
||||
// clear config parameters
|
||||
RtlZeroMemory(&m_rendererInfo, sizeof(m_rendererInfo));
|
||||
|
||||
// clear mouse information parameters
|
||||
if (m_mouseInfo.PtrShapeBuffer != nullptr) {
|
||||
delete[] m_mouseInfo.PtrShapeBuffer;
|
||||
m_mouseInfo.PtrShapeBuffer = nullptr;
|
||||
}
|
||||
RtlZeroMemory(&m_mouseInfo, sizeof(m_mouseInfo));
|
||||
|
||||
// clear temp temp buffer
|
||||
if (m_tempMouseBuffer.Buffer != nullptr) {
|
||||
delete[] m_tempMouseBuffer.Buffer;
|
||||
m_tempMouseBuffer.Buffer = nullptr;
|
||||
}
|
||||
RtlZeroMemory(&m_tempMouseBuffer, sizeof(m_tempMouseBuffer));
|
||||
|
||||
// clear desktop output desc
|
||||
RtlZeroMemory(&m_desktopOutputDesc, sizeof(m_desktopOutputDesc));
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::Initialize()
|
||||
{
|
||||
AUTOLOCK();
|
||||
if (m_bInitialized) {
|
||||
return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); // already initialized
|
||||
}
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
D3D_FEATURE_LEVEL lFeatureLevel;
|
||||
CComPtr<ID3D11Device> ipDevice;
|
||||
CComPtr<ID3D11DeviceContext> ipDeviceContext;
|
||||
|
||||
// required for monitor dpi problem (???)
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
|
||||
// Create device
|
||||
for (UINT i = 0; i < g_NumDriverTypes; ++i)
|
||||
{
|
||||
hr = D3D11CreateDevice(
|
||||
nullptr,
|
||||
g_DriverTypes[i],
|
||||
nullptr,
|
||||
/* D3D11_CREATE_DEVICE_BGRA_SUPPORT
|
||||
* This flag adds support for surfaces with a different
|
||||
* color channel ordering than the API default.
|
||||
* You need it for compatibility with Direct2D. */
|
||||
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
g_FeatureLevels,
|
||||
g_NumFeatureLevels,
|
||||
D3D11_SDK_VERSION,
|
||||
&ipDevice,
|
||||
&lFeatureLevel,
|
||||
&ipDeviceContext);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// Device creation success, no need to loop anymore
|
||||
break;
|
||||
}
|
||||
|
||||
ipDevice = nullptr;
|
||||
ipDeviceContext = nullptr;
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (nullptr == ipDevice) {
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// load all monitor informations
|
||||
hr = loadMonitorInfos(ipDevice);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// set common fields
|
||||
m_lD3DFeatureLevel = lFeatureLevel;
|
||||
m_ipD3D11Device = ipDevice;
|
||||
m_ipD3D11DeviceContext = ipDeviceContext;
|
||||
|
||||
m_bInitialized = TRUE;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::Terminate()
|
||||
{
|
||||
AUTOLOCK();
|
||||
if (!m_bInitialized) {
|
||||
return S_FALSE; // already terminated
|
||||
}
|
||||
|
||||
this->terminateDeviceResource();
|
||||
|
||||
m_ipD3D11Device = nullptr;
|
||||
m_ipD3D11DeviceContext = nullptr;
|
||||
m_lD3DFeatureLevel = D3D_FEATURE_LEVEL_INVALID;
|
||||
|
||||
freeMonitorInfos();
|
||||
|
||||
m_bInitialized = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::SetConfig(const tagScreenCaptureFilterConfig *pConfig)
|
||||
{
|
||||
AUTOLOCK();
|
||||
if (!m_bInitialized) {
|
||||
return D2DERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (nullptr == pConfig) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
// terminate old resources
|
||||
this->terminateDeviceResource();
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
const tagDublicatorMonitorInfo *pSelectedMonitorInfo = nullptr;
|
||||
|
||||
pSelectedMonitorInfo = this->FindDublicatorMonitorInfo(pConfig->MonitorIdx);
|
||||
if (nullptr == pSelectedMonitorInfo) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
hr = this->createDeviceResource(pConfig, pSelectedMonitorInfo);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT CDXGICapture::SetConfig(const tagScreenCaptureFilterConfig &config)
|
||||
{
|
||||
return this->SetConfig(&config);
|
||||
}
|
||||
|
||||
BOOL CDXGICapture::IsInitialized() const
|
||||
{
|
||||
AUTOLOCK();
|
||||
return m_bInitialized;
|
||||
}
|
||||
|
||||
D3D_FEATURE_LEVEL CDXGICapture::GetD3DFeatureLevel() const
|
||||
{
|
||||
AUTOLOCK();
|
||||
return m_lD3DFeatureLevel;
|
||||
}
|
||||
|
||||
int CDXGICapture::GetDublicatorMonitorInfoCount() const
|
||||
{
|
||||
AUTOLOCK();
|
||||
return (int)m_monitorInfos.size();
|
||||
}
|
||||
|
||||
const tagDublicatorMonitorInfo* CDXGICapture::GetDublicatorMonitorInfo(int index) const
|
||||
{
|
||||
AUTOLOCK();
|
||||
|
||||
size_t nCount = m_monitorInfos.size();
|
||||
if ((index < 0) || (index >= (int)nCount)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_monitorInfos[index];
|
||||
} // GetDublicatorMonitorInfo
|
||||
|
||||
const tagDublicatorMonitorInfo* CDXGICapture::FindDublicatorMonitorInfo(int monitorIdx) const
|
||||
{
|
||||
AUTOLOCK();
|
||||
|
||||
size_t nCount = m_monitorInfos.size();
|
||||
if (nCount == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
DublicatorMonitorInfoVec::const_iterator it = m_monitorInfos.begin();
|
||||
DublicatorMonitorInfoVec::const_iterator end = m_monitorInfos.end();
|
||||
for (size_t i = 0; (i < nCount) && (it != end); i++, it++) {
|
||||
tagDublicatorMonitorInfo *pInfo = *it;
|
||||
if (monitorIdx == pInfo->Idx) {
|
||||
return pInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
} // FindDublicatorMonitorInfo
|
||||
|
||||
//
|
||||
// CaptureToFile
|
||||
//
|
||||
HRESULT CDXGICapture::CaptureToFile(_In_ LPCWSTR lpcwOutputFileName, _Out_opt_ BOOL *pRetIsTimeout /*= NULL*/, _Out_opt_ UINT *pRetRenderDuration /*= NULL*/)
|
||||
{
|
||||
AUTOLOCK();
|
||||
|
||||
if (nullptr != pRetIsTimeout) {
|
||||
*pRetIsTimeout = FALSE;
|
||||
}
|
||||
|
||||
if (nullptr != pRetRenderDuration) {
|
||||
*pRetRenderDuration = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
if (!m_bInitialized) {
|
||||
return D2DERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
CHECK_POINTER_EX(m_ipDxgiOutputDuplication, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(lpcwOutputFileName, E_INVALIDARG);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = DXGICaptureHelper::IsRendererInfoValid(&m_rendererInfo);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// is valid?
|
||||
hr = DXGICaptureHelper::GetContainerFormatByFileName(lpcwOutputFileName);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
|
||||
CComPtr<IDXGIResource> ipDesktopResource;
|
||||
CComPtr<ID3D11Texture2D> ipAcquiredDesktopImage;
|
||||
CComPtr<ID2D1Bitmap> ipD2D1SourceBitmap;
|
||||
|
||||
std::chrono::system_clock::time_point startTick;
|
||||
if (nullptr != pRetRenderDuration) {
|
||||
startTick = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
// Get new frame
|
||||
hr = m_ipDxgiOutputDuplication->AcquireNextFrame(1000, &FrameInfo, &ipDesktopResource);
|
||||
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
|
||||
{
|
||||
if (nullptr != pRetIsTimeout) {
|
||||
*pRetIsTimeout = TRUE;
|
||||
}
|
||||
return S_FALSE;
|
||||
}
|
||||
else if (FAILED(hr))
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
|
||||
// QI for ID3D11Texture2D
|
||||
hr = ipDesktopResource->QueryInterface(IID_PPV_ARGS(&ipAcquiredDesktopImage));
|
||||
ipDesktopResource = nullptr;
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
if (nullptr == ipAcquiredDesktopImage)
|
||||
{
|
||||
// release frame
|
||||
m_ipDxgiOutputDuplication->ReleaseFrame();
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// Copy needed full part of desktop image
|
||||
m_ipD3D11DeviceContext->CopyResource(m_ipCopyTexture2D, ipAcquiredDesktopImage);
|
||||
|
||||
if (m_rendererInfo.ShowCursor) {
|
||||
hr = DXGICaptureHelper::GetMouse(m_ipDxgiOutputDuplication, &m_mouseInfo, &FrameInfo, (UINT)m_rendererInfo.MonitorIdx, m_desktopOutputDesc.DesktopCoordinates.left, m_desktopOutputDesc.DesktopCoordinates.top);
|
||||
if (SUCCEEDED(hr) && m_mouseInfo.Visible) {
|
||||
hr = DXGICaptureHelper::DrawMouse(&m_mouseInfo, &m_desktopOutputDesc, &m_tempMouseBuffer, m_ipCopyTexture2D);
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
// release frame
|
||||
m_ipDxgiOutputDuplication->ReleaseFrame();
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
// release frame
|
||||
hr = m_ipDxgiOutputDuplication->ReleaseFrame();
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
// create D2D1 source bitmap
|
||||
hr = DXGICaptureHelper::CreateBitmap(m_ipD2D1RenderTarget, m_ipCopyTexture2D, &ipD2D1SourceBitmap);
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
D2D1_RECT_F rcSource = D2D1::RectF(
|
||||
(FLOAT)m_rendererInfo.SrcBounds.X,
|
||||
(FLOAT)m_rendererInfo.SrcBounds.Y,
|
||||
(FLOAT)(m_rendererInfo.SrcBounds.X + m_rendererInfo.SrcBounds.Width),
|
||||
(FLOAT)(m_rendererInfo.SrcBounds.Y + m_rendererInfo.SrcBounds.Height));
|
||||
D2D1_RECT_F rcTarget = D2D1::RectF(
|
||||
(FLOAT)m_rendererInfo.DstBounds.X,
|
||||
(FLOAT)m_rendererInfo.DstBounds.Y,
|
||||
(FLOAT)(m_rendererInfo.DstBounds.X + m_rendererInfo.DstBounds.Width),
|
||||
(FLOAT)(m_rendererInfo.DstBounds.Y + m_rendererInfo.DstBounds.Height));
|
||||
D2D1_POINT_2F ptTransformCenter = D2D1::Point2F(m_rendererInfo.OutputSize.Width / 2.0f, m_rendererInfo.OutputSize.Height / 2.0f);
|
||||
|
||||
// Apply the rotation transform to the render target.
|
||||
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(
|
||||
m_rendererInfo.RotationDegrees,
|
||||
ptTransformCenter
|
||||
);
|
||||
|
||||
D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(
|
||||
D2D1::SizeF(m_rendererInfo.ScaleX, m_rendererInfo.ScaleY),
|
||||
ptTransformCenter
|
||||
);
|
||||
|
||||
// Priority: first rotate, after scale...
|
||||
m_ipD2D1RenderTarget->SetTransform(rotate * scale);
|
||||
|
||||
m_ipD2D1RenderTarget->BeginDraw();
|
||||
// clear background color
|
||||
m_ipD2D1RenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black, 1.0f));
|
||||
m_ipD2D1RenderTarget->DrawBitmap(ipD2D1SourceBitmap, rcTarget, 1.0f,
|
||||
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rcSource);
|
||||
// Reset transform
|
||||
//m_ipD2D1RenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||
// Logo draw sample
|
||||
//m_ipD2D1RenderTarget->DrawBitmap(ipBmpLogo, D2D1::RectF(0, 0, 2 * 200, 2 * 46));
|
||||
hr = m_ipD2D1RenderTarget->EndDraw();
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// calculate render time without save
|
||||
if (nullptr != pRetRenderDuration) {
|
||||
*pRetRenderDuration = (UINT)((std::chrono::system_clock::now() - startTick).count() / 10000);
|
||||
}
|
||||
hr = DXGICaptureHelper::SaveImageToFile(m_ipWICImageFactory,
|
||||
m_ipWICOutputBitmap, lpcwOutputFileName);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
} // CaptureToFile
|
||||
|
||||
#undef AUTOLOCK
|
|
@ -0,0 +1,92 @@
|
|||
/*****************************************************************************
|
||||
* DXGICapture.h
|
||||
*
|
||||
* Copyright (C) 2020 Gokhan Erdogdu <gokhan_erdogdu - at - yahoo - dot - com>
|
||||
*
|
||||
* DXGICapture is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* DXGICapture is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
******************************************************************************/
|
||||
#pragma once
|
||||
#ifndef __DXGICAPTURE_H__
|
||||
#define __DXGICAPTURE_H__
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <ShellScalingAPI.h>
|
||||
|
||||
#include <dxgi1_2.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <d2d1_1.h> // for ID2D1Effect
|
||||
#include <wincodec.h>
|
||||
|
||||
#include "DXGICaptureTypes.h"
|
||||
|
||||
#define D3D_FEATURE_LEVEL_INVALID ((D3D_FEATURE_LEVEL)0x0)
|
||||
|
||||
class CDXGICapture
|
||||
{
|
||||
private:
|
||||
ATL::CComAutoCriticalSection m_csLock;
|
||||
|
||||
BOOL m_bInitialized;
|
||||
DublicatorMonitorInfoVec m_monitorInfos;
|
||||
tagRendererInfo m_rendererInfo;
|
||||
|
||||
tagMouseInfo m_mouseInfo;
|
||||
tagFrameBufferInfo m_tempMouseBuffer;
|
||||
DXGI_OUTPUT_DESC m_desktopOutputDesc;
|
||||
|
||||
D3D_FEATURE_LEVEL m_lD3DFeatureLevel;
|
||||
CComPtr<ID3D11Device> m_ipD3D11Device;
|
||||
CComPtr<ID3D11DeviceContext> m_ipD3D11DeviceContext;
|
||||
|
||||
CComPtr<IDXGIOutputDuplication> m_ipDxgiOutputDuplication;
|
||||
CComPtr<ID3D11Texture2D> m_ipCopyTexture2D;
|
||||
|
||||
CComPtr<ID2D1Device> m_ipD2D1Device;
|
||||
CComPtr<ID2D1Factory> m_ipD2D1Factory;
|
||||
CComPtr<IWICImagingFactory> m_ipWICImageFactory;
|
||||
CComPtr<IWICBitmap> m_ipWICOutputBitmap;
|
||||
CComPtr<ID2D1RenderTarget> m_ipD2D1RenderTarget;
|
||||
|
||||
public:
|
||||
CDXGICapture();
|
||||
~CDXGICapture();
|
||||
|
||||
private:
|
||||
HRESULT loadMonitorInfos(ID3D11Device *pDevice);
|
||||
void freeMonitorInfos();
|
||||
|
||||
HRESULT createDeviceResource(
|
||||
const tagScreenCaptureFilterConfig *pConfig,
|
||||
const tagDublicatorMonitorInfo *pSelectedMonitorInfo);
|
||||
void terminateDeviceResource();
|
||||
|
||||
public:
|
||||
HRESULT Initialize();
|
||||
HRESULT Terminate();
|
||||
HRESULT SetConfig(const tagScreenCaptureFilterConfig *pConfig);
|
||||
HRESULT SetConfig(const tagScreenCaptureFilterConfig &config);
|
||||
|
||||
BOOL IsInitialized() const;
|
||||
D3D_FEATURE_LEVEL GetD3DFeatureLevel() const;
|
||||
|
||||
int GetDublicatorMonitorInfoCount() const;
|
||||
const tagDublicatorMonitorInfo* GetDublicatorMonitorInfo(int index) const;
|
||||
const tagDublicatorMonitorInfo* FindDublicatorMonitorInfo(int monitorIdx) const;
|
||||
|
||||
HRESULT CaptureToFile(_In_ LPCWSTR lpcwOutputFileName, _Out_opt_ BOOL *pRetIsTimeout = NULL, _Out_opt_ UINT *pRetRenderDuration = NULL);
|
||||
};
|
||||
|
||||
#endif // __DXGICAPTURE_H__
|
||||
|
||||
|
|
@ -0,0 +1,960 @@
|
|||
/*****************************************************************************
|
||||
* DXGICaptureHelper.h
|
||||
*
|
||||
* Copyright (C) 2020 Gokhan Erdogdu <gokhan_erdogdu - at - yahoo - dot - com>
|
||||
*
|
||||
* DXGICapture is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* DXGICapture is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
******************************************************************************/
|
||||
#pragma once
|
||||
#ifndef __DXGICAPTUREHELPER_H__
|
||||
#define __DXGICAPTUREHELPER_H__
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <Shlwapi.h>
|
||||
|
||||
#include <dxgi1_2.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
#include "DXGICaptureTypes.h"
|
||||
|
||||
#pragma comment (lib, "Shlwapi.lib")
|
||||
|
||||
//
|
||||
// class DXGICaptureHelper
|
||||
//
|
||||
class DXGICaptureHelper
|
||||
{
|
||||
public:
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
ConvertDxgiOutputToMonitorInfo(
|
||||
_In_ const DXGI_OUTPUT_DESC *pDxgiOutput,
|
||||
_In_ int monitorIdx,
|
||||
_Out_ tagDublicatorMonitorInfo *pOutVal
|
||||
)
|
||||
{
|
||||
CHECK_POINTER(pOutVal);
|
||||
// reset output parameter
|
||||
RtlZeroMemory(pOutVal, sizeof(tagDublicatorMonitorInfo));
|
||||
CHECK_POINTER_EX(pDxgiOutput, E_INVALIDARG);
|
||||
|
||||
switch (pDxgiOutput->Rotation)
|
||||
{
|
||||
case DXGI_MODE_ROTATION_UNSPECIFIED:
|
||||
case DXGI_MODE_ROTATION_IDENTITY:
|
||||
pOutVal->RotationDegrees = 0;
|
||||
break;
|
||||
|
||||
case DXGI_MODE_ROTATION_ROTATE90:
|
||||
pOutVal->RotationDegrees = 90;
|
||||
break;
|
||||
|
||||
case DXGI_MODE_ROTATION_ROTATE180:
|
||||
pOutVal->RotationDegrees = 180;
|
||||
break;
|
||||
|
||||
case DXGI_MODE_ROTATION_ROTATE270:
|
||||
pOutVal->RotationDegrees = 270;
|
||||
break;
|
||||
}
|
||||
|
||||
pOutVal->Idx = monitorIdx;
|
||||
pOutVal->Bounds.X = pDxgiOutput->DesktopCoordinates.left;
|
||||
pOutVal->Bounds.Y = pDxgiOutput->DesktopCoordinates.top;
|
||||
pOutVal->Bounds.Width = pDxgiOutput->DesktopCoordinates.right - pDxgiOutput->DesktopCoordinates.left;
|
||||
pOutVal->Bounds.Height = pDxgiOutput->DesktopCoordinates.bottom - pDxgiOutput->DesktopCoordinates.top;
|
||||
|
||||
wsprintfW(pOutVal->DisplayName, L"Display %d: %ldx%ld @ %ld,%ld"
|
||||
, monitorIdx + 1
|
||||
, pOutVal->Bounds.Width, pOutVal->Bounds.Height
|
||||
, pOutVal->Bounds.X, pOutVal->Bounds.Y);
|
||||
|
||||
return S_OK;
|
||||
} // ConvertDxgiOutputToMonitorInfo
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
BOOL
|
||||
IsEqualMonitorInfo(
|
||||
_In_ const tagDublicatorMonitorInfo *p1,
|
||||
_In_ const tagDublicatorMonitorInfo *p2
|
||||
)
|
||||
{
|
||||
if (nullptr == p1) {
|
||||
return (nullptr == p2);
|
||||
}
|
||||
if (nullptr == p2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return memcmp((const void*)p1, (const void*)p2, sizeof(tagDublicatorMonitorInfo)) == 0;
|
||||
} // IsEqualMonitorInfo
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
IsRendererInfoValid(
|
||||
_In_ const tagRendererInfo *pRendererInfo
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(pRendererInfo, E_INVALIDARG);
|
||||
|
||||
if (pRendererInfo->SrcFormat != DXGI_FORMAT_B8G8R8A8_UNORM) {
|
||||
return D2DERR_UNSUPPORTED_PIXEL_FORMAT;
|
||||
}
|
||||
|
||||
if (pRendererInfo->SizeMode != tagFrameSizeMode_Normal) {
|
||||
if ((pRendererInfo->OutputSize.Width <= 0) || (pRendererInfo->OutputSize.Height <= 0)) {
|
||||
return D2DERR_BITMAP_BOUND_AS_TARGET;
|
||||
}
|
||||
}
|
||||
|
||||
if ((pRendererInfo->DstBounds.Width <= 0) || (pRendererInfo->DstBounds.Height <= 0) ||
|
||||
(pRendererInfo->SrcBounds.Width <= 0) || (pRendererInfo->SrcBounds.Height <= 0))
|
||||
{
|
||||
return D2DERR_ORIGINAL_TARGET_NOT_BOUND;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
CalculateRendererInfo(
|
||||
_In_ const DXGI_OUTDUPL_DESC *pDxgiOutputDuplDesc,
|
||||
_Inout_ tagRendererInfo *pRendererInfo
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(pDxgiOutputDuplDesc, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pRendererInfo, E_INVALIDARG);
|
||||
|
||||
pRendererInfo->SrcFormat = pDxgiOutputDuplDesc->ModeDesc.Format;
|
||||
// get rotate state
|
||||
switch (pDxgiOutputDuplDesc->Rotation)
|
||||
{
|
||||
case DXGI_MODE_ROTATION_ROTATE90:
|
||||
pRendererInfo->RotationDegrees = 90.0f;
|
||||
pRendererInfo->SrcBounds.X = 0;
|
||||
pRendererInfo->SrcBounds.Y = 0;
|
||||
pRendererInfo->SrcBounds.Width = pDxgiOutputDuplDesc->ModeDesc.Height;
|
||||
pRendererInfo->SrcBounds.Height = pDxgiOutputDuplDesc->ModeDesc.Width;
|
||||
break;
|
||||
case DXGI_MODE_ROTATION_ROTATE180:
|
||||
pRendererInfo->RotationDegrees = 180.0;
|
||||
pRendererInfo->SrcBounds.X = 0;
|
||||
pRendererInfo->SrcBounds.Y = 0;
|
||||
pRendererInfo->SrcBounds.Width = pDxgiOutputDuplDesc->ModeDesc.Width;
|
||||
pRendererInfo->SrcBounds.Height = pDxgiOutputDuplDesc->ModeDesc.Height;
|
||||
break;
|
||||
case DXGI_MODE_ROTATION_ROTATE270:
|
||||
pRendererInfo->RotationDegrees = 270.0f;
|
||||
pRendererInfo->SrcBounds.X = 0;
|
||||
pRendererInfo->SrcBounds.Y = 0;
|
||||
pRendererInfo->SrcBounds.Width = pDxgiOutputDuplDesc->ModeDesc.Height;
|
||||
pRendererInfo->SrcBounds.Height = pDxgiOutputDuplDesc->ModeDesc.Width;
|
||||
break;
|
||||
default: // OR DXGI_MODE_ROTATION_IDENTITY:
|
||||
pRendererInfo->RotationDegrees = 0.0f;
|
||||
pRendererInfo->SrcBounds.X = 0;
|
||||
pRendererInfo->SrcBounds.Y = 0;
|
||||
pRendererInfo->SrcBounds.Width = pDxgiOutputDuplDesc->ModeDesc.Width;
|
||||
pRendererInfo->SrcBounds.Height = pDxgiOutputDuplDesc->ModeDesc.Height;
|
||||
break;
|
||||
}
|
||||
|
||||
// force rotate
|
||||
switch (pRendererInfo->RotationMode)
|
||||
{
|
||||
case tagFrameRotationMode::tagFrameRotationMode_Identity:
|
||||
pRendererInfo->RotationDegrees = 0.0f;
|
||||
break;
|
||||
case tagFrameRotationMode::tagFrameRotationMode_90:
|
||||
pRendererInfo->RotationDegrees = 90.0f;
|
||||
break;
|
||||
case tagFrameRotationMode::tagFrameRotationMode_180:
|
||||
pRendererInfo->RotationDegrees = 180.0f;
|
||||
break;
|
||||
case tagFrameRotationMode::tagFrameRotationMode_270:
|
||||
pRendererInfo->RotationDegrees = 270.0f;
|
||||
break;
|
||||
default: // tagFrameRotationMode::tagFrameRotationMode_Auto
|
||||
break;
|
||||
}
|
||||
|
||||
if (pRendererInfo->SizeMode == tagFrameSizeMode_Zoom)
|
||||
{
|
||||
FLOAT fSrcAspect, fOutAspect, fScaleFactor;
|
||||
|
||||
// center for output
|
||||
pRendererInfo->DstBounds.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->DstBounds.Height = pRendererInfo->SrcBounds.Height;
|
||||
pRendererInfo->DstBounds.X = (pRendererInfo->OutputSize.Width - pRendererInfo->SrcBounds.Width) >> 1;
|
||||
pRendererInfo->DstBounds.Y = (pRendererInfo->OutputSize.Height - pRendererInfo->SrcBounds.Height) >> 1;
|
||||
|
||||
fOutAspect = (FLOAT)pRendererInfo->OutputSize.Width / pRendererInfo->OutputSize.Height;
|
||||
|
||||
if ((pRendererInfo->RotationDegrees == 0.0f) || (pRendererInfo->RotationDegrees == 180.0f))
|
||||
{
|
||||
fSrcAspect = (FLOAT)pRendererInfo->SrcBounds.Width / pRendererInfo->SrcBounds.Height;
|
||||
|
||||
if (fSrcAspect > fOutAspect)
|
||||
{
|
||||
fScaleFactor = (FLOAT)pRendererInfo->OutputSize.Width / pRendererInfo->SrcBounds.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
fScaleFactor = (FLOAT)pRendererInfo->OutputSize.Height / pRendererInfo->SrcBounds.Height;
|
||||
}
|
||||
}
|
||||
else // 90 or 270 degree
|
||||
{
|
||||
fSrcAspect = (FLOAT)pRendererInfo->SrcBounds.Height / pRendererInfo->SrcBounds.Width;
|
||||
|
||||
if (fSrcAspect > fOutAspect)
|
||||
{
|
||||
fScaleFactor = (FLOAT)pRendererInfo->OutputSize.Width / pRendererInfo->SrcBounds.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
fScaleFactor = (FLOAT)pRendererInfo->OutputSize.Height / pRendererInfo->SrcBounds.Width;
|
||||
}
|
||||
}
|
||||
|
||||
pRendererInfo->ScaleX = fScaleFactor;
|
||||
pRendererInfo->ScaleY = fScaleFactor;
|
||||
}
|
||||
else if (pRendererInfo->SizeMode == tagFrameSizeMode_CenterImage)
|
||||
{
|
||||
// center for output
|
||||
pRendererInfo->DstBounds.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->DstBounds.Height = pRendererInfo->SrcBounds.Height;
|
||||
pRendererInfo->DstBounds.X = (pRendererInfo->OutputSize.Width - pRendererInfo->SrcBounds.Width) >> 1;
|
||||
pRendererInfo->DstBounds.Y = (pRendererInfo->OutputSize.Height - pRendererInfo->SrcBounds.Height) >> 1;
|
||||
}
|
||||
else if (pRendererInfo->SizeMode == tagFrameSizeMode_AutoSize)
|
||||
{
|
||||
// set the destination bounds
|
||||
pRendererInfo->DstBounds.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->DstBounds.Height = pRendererInfo->SrcBounds.Height;
|
||||
|
||||
if ((pRendererInfo->RotationDegrees == 0.0f) || (pRendererInfo->RotationDegrees == 180.0f))
|
||||
{
|
||||
// same as the source size
|
||||
pRendererInfo->OutputSize.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->OutputSize.Height = pRendererInfo->SrcBounds.Height;
|
||||
}
|
||||
else // 90 or 270 degree
|
||||
{
|
||||
// same as the source size
|
||||
pRendererInfo->OutputSize.Width = pRendererInfo->SrcBounds.Height;
|
||||
pRendererInfo->OutputSize.Height = pRendererInfo->SrcBounds.Width;
|
||||
|
||||
// center for output
|
||||
pRendererInfo->DstBounds.X = (pRendererInfo->OutputSize.Width - pRendererInfo->SrcBounds.Width) >> 1;
|
||||
pRendererInfo->DstBounds.Y = (pRendererInfo->OutputSize.Height - pRendererInfo->SrcBounds.Height) >> 1;
|
||||
}
|
||||
}
|
||||
else if (pRendererInfo->SizeMode == tagFrameSizeMode_StretchImage)
|
||||
{
|
||||
// center for output
|
||||
pRendererInfo->DstBounds.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->DstBounds.Height = pRendererInfo->SrcBounds.Height;
|
||||
pRendererInfo->DstBounds.X = (pRendererInfo->OutputSize.Width - pRendererInfo->SrcBounds.Width) >> 1;
|
||||
pRendererInfo->DstBounds.Y = (pRendererInfo->OutputSize.Height - pRendererInfo->SrcBounds.Height) >> 1;
|
||||
|
||||
if ((pRendererInfo->RotationDegrees == 0.0f) || (pRendererInfo->RotationDegrees == 180.0f))
|
||||
{
|
||||
pRendererInfo->ScaleX = (FLOAT)pRendererInfo->OutputSize.Width / pRendererInfo->DstBounds.Width;
|
||||
pRendererInfo->ScaleY = (FLOAT)pRendererInfo->OutputSize.Height / pRendererInfo->DstBounds.Height;
|
||||
}
|
||||
else // 90 or 270 degree
|
||||
{
|
||||
pRendererInfo->ScaleX = (FLOAT)pRendererInfo->OutputSize.Width / pRendererInfo->DstBounds.Height;
|
||||
pRendererInfo->ScaleY = (FLOAT)pRendererInfo->OutputSize.Height / pRendererInfo->DstBounds.Width;
|
||||
}
|
||||
}
|
||||
else // tagFrameSizeMode_Normal
|
||||
{
|
||||
pRendererInfo->DstBounds.Width = pRendererInfo->SrcBounds.Width;
|
||||
pRendererInfo->DstBounds.Height = pRendererInfo->SrcBounds.Height;
|
||||
|
||||
if (pRendererInfo->RotationDegrees == 90)
|
||||
{
|
||||
// set destination origin (bottom-left)
|
||||
pRendererInfo->DstBounds.X = (pRendererInfo->OutputSize.Width - pRendererInfo->OutputSize.Height) >> 1;
|
||||
pRendererInfo->DstBounds.Y = ((pRendererInfo->OutputSize.Width + pRendererInfo->OutputSize.Height) >> 1) - pRendererInfo->DstBounds.Height;
|
||||
}
|
||||
else if (pRendererInfo->RotationDegrees == 180.0f)
|
||||
{
|
||||
// set destination origin (bottom-right)
|
||||
pRendererInfo->DstBounds.X = pRendererInfo->OutputSize.Width - pRendererInfo->DstBounds.Width;
|
||||
pRendererInfo->DstBounds.Y = pRendererInfo->OutputSize.Height - pRendererInfo->DstBounds.Height;
|
||||
}
|
||||
else if (pRendererInfo->RotationDegrees == 270)
|
||||
{
|
||||
// set destination origin (top-right)
|
||||
pRendererInfo->DstBounds.Y = (pRendererInfo->OutputSize.Height - pRendererInfo->OutputSize.Width) >> 1;
|
||||
pRendererInfo->DstBounds.X = pRendererInfo->OutputSize.Width - pRendererInfo->DstBounds.Width - ((pRendererInfo->OutputSize.Width - pRendererInfo->OutputSize.Height) >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
ResizeFrameBuffer(
|
||||
_Inout_ tagFrameBufferInfo *pBufferInfo,
|
||||
_In_ UINT uiNewSize
|
||||
)
|
||||
{
|
||||
CHECK_POINTER(pBufferInfo);
|
||||
|
||||
if (uiNewSize <= pBufferInfo->BufferSize)
|
||||
{
|
||||
return S_FALSE; // no change
|
||||
}
|
||||
|
||||
if (nullptr != pBufferInfo->Buffer) {
|
||||
delete[] pBufferInfo->Buffer;
|
||||
pBufferInfo->Buffer = nullptr;
|
||||
}
|
||||
|
||||
pBufferInfo->Buffer = new (std::nothrow) BYTE[uiNewSize];
|
||||
if (!(pBufferInfo->Buffer))
|
||||
{
|
||||
pBufferInfo->BufferSize = 0;
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
pBufferInfo->BufferSize = uiNewSize;
|
||||
|
||||
return S_OK;
|
||||
} // ResizeFrameBuffer
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
GetMouse(
|
||||
_In_ IDXGIOutputDuplication *pOutputDuplication,
|
||||
_Inout_ tagMouseInfo *PtrInfo,
|
||||
_In_ DXGI_OUTDUPL_FRAME_INFO *FrameInfo,
|
||||
UINT MonitorIdx,
|
||||
INT OffsetX,
|
||||
INT OffsetY
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(pOutputDuplication, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(PtrInfo, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(FrameInfo, E_INVALIDARG);
|
||||
|
||||
// A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change
|
||||
if (FrameInfo->LastMouseUpdateTime.QuadPart == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool UpdatePosition = true;
|
||||
|
||||
// Make sure we don't update pointer position wrongly
|
||||
// If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer
|
||||
// was visible, if so, don't set it to invisible or update.
|
||||
if (!FrameInfo->PointerPosition.Visible && (PtrInfo->WhoUpdatedPositionLast != MonitorIdx))
|
||||
{
|
||||
UpdatePosition = false;
|
||||
}
|
||||
|
||||
// If two outputs both say they have a visible, only update if new update has newer timestamp
|
||||
if (FrameInfo->PointerPosition.Visible && PtrInfo->Visible && (PtrInfo->WhoUpdatedPositionLast != MonitorIdx) && (PtrInfo->LastTimeStamp.QuadPart > FrameInfo->LastMouseUpdateTime.QuadPart))
|
||||
{
|
||||
UpdatePosition = false;
|
||||
}
|
||||
|
||||
// Update position
|
||||
if (UpdatePosition)
|
||||
{
|
||||
PtrInfo->Position.x = FrameInfo->PointerPosition.Position.x - OffsetX;
|
||||
PtrInfo->Position.y = FrameInfo->PointerPosition.Position.y - OffsetY;
|
||||
PtrInfo->WhoUpdatedPositionLast = MonitorIdx;
|
||||
PtrInfo->LastTimeStamp = FrameInfo->LastMouseUpdateTime;
|
||||
PtrInfo->Visible = FrameInfo->PointerPosition.Visible != 0;
|
||||
}
|
||||
|
||||
// No new shape
|
||||
if (FrameInfo->PointerShapeBufferSize == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Old buffer too small
|
||||
if (FrameInfo->PointerShapeBufferSize > PtrInfo->ShapeBufferSize)
|
||||
{
|
||||
if (PtrInfo->PtrShapeBuffer != nullptr)
|
||||
{
|
||||
delete[] PtrInfo->PtrShapeBuffer;
|
||||
PtrInfo->PtrShapeBuffer = nullptr;
|
||||
}
|
||||
PtrInfo->PtrShapeBuffer = new (std::nothrow) BYTE[FrameInfo->PointerShapeBufferSize];
|
||||
if (PtrInfo->PtrShapeBuffer == nullptr)
|
||||
{
|
||||
PtrInfo->ShapeBufferSize = 0;
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// Update buffer size
|
||||
PtrInfo->ShapeBufferSize = FrameInfo->PointerShapeBufferSize;
|
||||
}
|
||||
|
||||
// Get shape
|
||||
UINT BufferSizeRequired;
|
||||
HRESULT hr = pOutputDuplication->GetFramePointerShape(
|
||||
FrameInfo->PointerShapeBufferSize,
|
||||
reinterpret_cast<VOID*>(PtrInfo->PtrShapeBuffer),
|
||||
&BufferSizeRequired,
|
||||
&(PtrInfo->ShapeInfo)
|
||||
);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
delete[] PtrInfo->PtrShapeBuffer;
|
||||
PtrInfo->PtrShapeBuffer = nullptr;
|
||||
PtrInfo->ShapeBufferSize = 0;
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
} // GetMouse
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
ProcessMouseMask(
|
||||
_In_ const tagMouseInfo *PtrInfo,
|
||||
_In_ const DXGI_OUTPUT_DESC *DesktopDesc,
|
||||
_Inout_ tagFrameBufferInfo *pBufferInfo
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(PtrInfo, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(DesktopDesc, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pBufferInfo, E_INVALIDARG);
|
||||
|
||||
if (!PtrInfo->Visible) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
INT DesktopWidth = (INT)(DesktopDesc->DesktopCoordinates.right - DesktopDesc->DesktopCoordinates.left);
|
||||
INT DesktopHeight = (INT)(DesktopDesc->DesktopCoordinates.bottom - DesktopDesc->DesktopCoordinates.top);
|
||||
|
||||
pBufferInfo->Bounds.X = PtrInfo->Position.x;
|
||||
pBufferInfo->Bounds.Y = PtrInfo->Position.y;
|
||||
pBufferInfo->Bounds.Width = PtrInfo->ShapeInfo.Width;
|
||||
pBufferInfo->Bounds.Height = (PtrInfo->ShapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME)
|
||||
? (INT)(PtrInfo->ShapeInfo.Height / 2)
|
||||
: (INT)PtrInfo->ShapeInfo.Height;
|
||||
pBufferInfo->Pitch = pBufferInfo->Bounds.Width * 4;
|
||||
|
||||
switch (PtrInfo->ShapeInfo.Type)
|
||||
{
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
{
|
||||
// Resize mouseshape buffer (if necessary)
|
||||
hr = DXGICaptureHelper::ResizeFrameBuffer(pBufferInfo, PtrInfo->ShapeBufferSize);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// use current mouseshape buffer
|
||||
// Copy mouseshape buffer
|
||||
memcpy_s((void*)pBufferInfo->Buffer, pBufferInfo->BufferSize, (const void*)PtrInfo->PtrShapeBuffer, PtrInfo->ShapeBufferSize);
|
||||
break;
|
||||
}
|
||||
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
|
||||
{
|
||||
// Resize mouseshape buffer (if necessary)
|
||||
hr = DXGICaptureHelper::ResizeFrameBuffer(pBufferInfo, pBufferInfo->Bounds.Height * pBufferInfo->Pitch);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
UINT* InitBuffer32 = reinterpret_cast<UINT*>(pBufferInfo->Buffer);
|
||||
|
||||
for (INT Row = 0; Row < pBufferInfo->Bounds.Height; ++Row)
|
||||
{
|
||||
// Set mask
|
||||
BYTE Mask = 0x80;
|
||||
for (INT Col = 0; Col < pBufferInfo->Bounds.Width; ++Col)
|
||||
{
|
||||
BYTE XorMask = PtrInfo->PtrShapeBuffer[(Col / 8) + ((Row + (PtrInfo->ShapeInfo.Height / 2)) * (PtrInfo->ShapeInfo.Pitch))] & Mask;
|
||||
|
||||
// Set new pixel
|
||||
InitBuffer32[(Row * pBufferInfo->Bounds.Width) + Col] = (XorMask) ? 0xFFFFFFFF : 0x00000000;
|
||||
|
||||
// Adjust mask
|
||||
if (Mask == 0x01)
|
||||
{
|
||||
Mask = 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
Mask = Mask >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
|
||||
{
|
||||
// Resize mouseshape buffer (if necessary)
|
||||
hr = DXGICaptureHelper::ResizeFrameBuffer(pBufferInfo, pBufferInfo->Bounds.Height * pBufferInfo->Pitch);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
UINT* InitBuffer32 = reinterpret_cast<UINT*>(pBufferInfo->Buffer);
|
||||
UINT* ShapeBuffer32 = reinterpret_cast<UINT*>(PtrInfo->PtrShapeBuffer);
|
||||
|
||||
for (INT Row = 0; Row < pBufferInfo->Bounds.Height; ++Row)
|
||||
{
|
||||
for (INT Col = 0; Col < pBufferInfo->Bounds.Width; ++Col)
|
||||
{
|
||||
InitBuffer32[(Row * pBufferInfo->Bounds.Width) + Col] = ShapeBuffer32[Col + (Row * (PtrInfo->ShapeInfo.Pitch / sizeof(UINT)))] | 0xFF000000;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return E_INVALIDARG;
|
||||
|
||||
}
|
||||
|
||||
UINT* InitBuffer32 = reinterpret_cast<UINT*>(pBufferInfo->Buffer);
|
||||
UINT width = (UINT)pBufferInfo->Bounds.Width;
|
||||
UINT height = (UINT)pBufferInfo->Bounds.Height;
|
||||
|
||||
switch (DesktopDesc->Rotation)
|
||||
{
|
||||
case DXGI_MODE_ROTATION_ROTATE90:
|
||||
{
|
||||
// Rotate -90 or +270
|
||||
for (UINT i = 0; i < width; i++)
|
||||
{
|
||||
for (UINT j = 0; j < height; j++)
|
||||
{
|
||||
UINT I = j;
|
||||
UINT J = width - 1 - i;
|
||||
while ((i*height + j) >(I*width + J))
|
||||
{
|
||||
UINT p = I*width + J;
|
||||
UINT tmp_i = p / height;
|
||||
UINT tmp_j = p % height;
|
||||
I = tmp_j;
|
||||
J = width - 1 - tmp_i;
|
||||
}
|
||||
std::swap(*(InitBuffer32 + (i*height + j)), *(InitBuffer32 + (I*width + J)));
|
||||
}
|
||||
}
|
||||
|
||||
// translate bounds
|
||||
std::swap(pBufferInfo->Bounds.Width, pBufferInfo->Bounds.Height);
|
||||
INT nX = pBufferInfo->Bounds.Y;
|
||||
INT nY = DesktopWidth - (INT)(pBufferInfo->Bounds.X + pBufferInfo->Bounds.Height);
|
||||
pBufferInfo->Bounds.X = nX;
|
||||
pBufferInfo->Bounds.Y = nY;
|
||||
pBufferInfo->Pitch = pBufferInfo->Bounds.Width * 4;
|
||||
} break;
|
||||
case DXGI_MODE_ROTATION_ROTATE180:
|
||||
{
|
||||
// Rotate -180 or +180
|
||||
if (height % 2 != 0)
|
||||
{
|
||||
//If N is odd reverse the middle row in the matrix
|
||||
UINT j = height >> 1;
|
||||
for (UINT i = 0; i < (width >> 1); i++)
|
||||
{
|
||||
std::swap(InitBuffer32[j * width + i], InitBuffer32[j * width + width - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (UINT j = 0; j < (height >> 1); j++)
|
||||
{
|
||||
for (UINT i = 0; i < width; i++)
|
||||
{
|
||||
std::swap(InitBuffer32[j * width + i], InitBuffer32[(height - j - 1) * width + width - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// translate position
|
||||
INT nX = DesktopWidth - (INT)(pBufferInfo->Bounds.X + pBufferInfo->Bounds.Width);
|
||||
INT nY = DesktopHeight - (INT)(pBufferInfo->Bounds.Y + pBufferInfo->Bounds.Height);
|
||||
pBufferInfo->Bounds.X = nX;
|
||||
pBufferInfo->Bounds.Y = nY;
|
||||
} break;
|
||||
case DXGI_MODE_ROTATION_ROTATE270:
|
||||
{
|
||||
// Rotate -270 or +90
|
||||
for (UINT i = 0; i < width; i++)
|
||||
{
|
||||
for (UINT j = 0; j < height; j++)
|
||||
{
|
||||
UINT I = height - 1 - j;
|
||||
UINT J = i;
|
||||
while ((i*height + j) >(I*width + J))
|
||||
{
|
||||
int p = I*width + J;
|
||||
int tmp_i = p / height;
|
||||
int tmp_j = p % height;
|
||||
I = height - 1 - tmp_j;
|
||||
J = tmp_i;
|
||||
}
|
||||
std::swap(*(InitBuffer32 + (i*height + j)), *(InitBuffer32 + (I*width + J)));
|
||||
}
|
||||
}
|
||||
|
||||
// translate bounds
|
||||
std::swap(pBufferInfo->Bounds.Width, pBufferInfo->Bounds.Height);
|
||||
INT nX = DesktopHeight - (pBufferInfo->Bounds.Y + pBufferInfo->Bounds.Width);
|
||||
INT nY = pBufferInfo->Bounds.X;
|
||||
pBufferInfo->Bounds.X = nX;
|
||||
pBufferInfo->Bounds.Y = nY;
|
||||
pBufferInfo->Pitch = pBufferInfo->Bounds.Width * 4;
|
||||
} break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
} // ProcessMouseMask
|
||||
|
||||
//
|
||||
// Draw mouse provided in buffer to backbuffer
|
||||
//
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
DrawMouse(
|
||||
_In_ tagMouseInfo *PtrInfo,
|
||||
_In_ const DXGI_OUTPUT_DESC *DesktopDesc,
|
||||
_Inout_ tagFrameBufferInfo *pTempMouseBuffer,
|
||||
_Inout_ ID3D11Texture2D *pSharedSurf
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(PtrInfo, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(DesktopDesc, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pTempMouseBuffer, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pSharedSurf, E_INVALIDARG);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
D3D11_TEXTURE2D_DESC FullDesc;
|
||||
pSharedSurf->GetDesc(&FullDesc);
|
||||
|
||||
INT SurfWidth = FullDesc.Width;
|
||||
INT SurfHeight = FullDesc.Height;
|
||||
INT SurfPitch = FullDesc.Width * 4;
|
||||
|
||||
hr = DXGICaptureHelper::ProcessMouseMask(PtrInfo, DesktopDesc, pTempMouseBuffer);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Buffer used if necessary (in case of monochrome or masked pointer)
|
||||
BYTE* InitBuffer = pTempMouseBuffer->Buffer;
|
||||
|
||||
// Clipping adjusted coordinates / dimensions
|
||||
INT PtrWidth = (INT)pTempMouseBuffer->Bounds.Width;
|
||||
INT PtrHeight = (INT)pTempMouseBuffer->Bounds.Height;
|
||||
|
||||
INT PtrLeft = (INT)pTempMouseBuffer->Bounds.X;
|
||||
INT PtrTop = (INT)pTempMouseBuffer->Bounds.Y;
|
||||
INT PtrPitch = (INT)pTempMouseBuffer->Pitch;
|
||||
|
||||
INT SrcLeft = 0;
|
||||
INT SrcTop = 0;
|
||||
INT SrcWidth = PtrWidth;
|
||||
INT SrcHeight = PtrHeight;
|
||||
|
||||
if (PtrLeft < 0)
|
||||
{
|
||||
// crop mouseshape left
|
||||
SrcLeft = -PtrLeft;
|
||||
// new mouse x position for drawing
|
||||
PtrLeft = 0;
|
||||
}
|
||||
else if (PtrLeft + PtrWidth > SurfWidth)
|
||||
{
|
||||
// crop mouseshape width
|
||||
SrcWidth = SurfWidth - PtrLeft;
|
||||
}
|
||||
|
||||
if (PtrTop < 0)
|
||||
{
|
||||
// crop mouseshape top
|
||||
SrcTop = -PtrTop;
|
||||
// new mouse y position for drawing
|
||||
PtrTop = 0;
|
||||
}
|
||||
else if (PtrTop + PtrHeight > SurfHeight)
|
||||
{
|
||||
// crop mouseshape height
|
||||
SrcHeight = SurfHeight - PtrTop;
|
||||
}
|
||||
|
||||
// QI for IDXGISurface
|
||||
CComPtr<IDXGISurface> ipCopySurface;
|
||||
hr = pSharedSurf->QueryInterface(__uuidof(IDXGISurface), (void **)&ipCopySurface);
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Map pixels
|
||||
DXGI_MAPPED_RECT MappedSurface;
|
||||
hr = ipCopySurface->Map(&MappedSurface, DXGI_MAP_READ | DXGI_MAP_WRITE);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// 0xAARRGGBB
|
||||
UINT* SrcBuffer32 = reinterpret_cast<UINT*>(InitBuffer);
|
||||
UINT* DstBuffer32 = reinterpret_cast<UINT*>(MappedSurface.pBits) + PtrTop * SurfWidth + PtrLeft;
|
||||
|
||||
// Alpha blending masks
|
||||
const UINT AMask = 0xFF000000;
|
||||
const UINT RBMask = 0x00FF00FF;
|
||||
const UINT GMask = 0x0000FF00;
|
||||
const UINT AGMask = AMask | GMask;
|
||||
const UINT OneAlpha = 0x01000000;
|
||||
UINT uiPixel1;
|
||||
UINT uiPixel2;
|
||||
UINT uiAlpha;
|
||||
UINT uiNAlpha;
|
||||
UINT uiRedBlue;
|
||||
UINT uiAlphaGreen;
|
||||
|
||||
for (INT Row = SrcTop; Row < SrcHeight; ++Row)
|
||||
{
|
||||
for (INT Col = SrcLeft; Col < SrcWidth; ++Col)
|
||||
{
|
||||
// Alpha blending
|
||||
uiPixel1 = DstBuffer32[((Row - SrcTop) * SurfWidth) + (Col - SrcLeft)];
|
||||
uiPixel2 = SrcBuffer32[(Row * PtrWidth) + Col];
|
||||
uiAlpha = (uiPixel2 & AMask) >> 24;
|
||||
uiNAlpha = 255 - uiAlpha;
|
||||
uiRedBlue = ((uiNAlpha * (uiPixel1 & RBMask)) + (uiAlpha * (uiPixel2 & RBMask))) >> 8;
|
||||
uiAlphaGreen = (uiNAlpha * ((uiPixel1 & AGMask) >> 8)) + (uiAlpha * (OneAlpha | ((uiPixel2 & GMask) >> 8)));
|
||||
|
||||
DstBuffer32[((Row - SrcTop) * SurfWidth) + (Col - SrcLeft)] = ((uiRedBlue & RBMask) | (uiAlphaGreen & AGMask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Done with resource
|
||||
hr = ipCopySurface->Unmap();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
} // DrawMouse
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
CreateBitmap(
|
||||
_In_ ID2D1RenderTarget *pRenderTarget,
|
||||
_In_ ID3D11Texture2D *pSourceTexture,
|
||||
_Outptr_ ID2D1Bitmap **ppOutBitmap
|
||||
)
|
||||
{
|
||||
CHECK_POINTER(ppOutBitmap);
|
||||
*ppOutBitmap = nullptr;
|
||||
CHECK_POINTER_EX(pRenderTarget, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pSourceTexture, E_INVALIDARG);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
CComPtr<ID3D11Texture2D> ipSourceTexture(pSourceTexture);
|
||||
CComPtr<IDXGISurface> ipCopySurface;
|
||||
CComPtr<ID2D1Bitmap> ipD2D1SourceBitmap;
|
||||
|
||||
// QI for IDXGISurface
|
||||
hr = ipSourceTexture->QueryInterface(__uuidof(IDXGISurface), (void **)&ipCopySurface);
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
// Map pixels
|
||||
DXGI_MAPPED_RECT MappedSurface;
|
||||
hr = ipCopySurface->Map(&MappedSurface, DXGI_MAP_READ);
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
D3D11_TEXTURE2D_DESC destImageDesc;
|
||||
ipSourceTexture->GetDesc(&destImageDesc);
|
||||
|
||||
hr = pRenderTarget->CreateBitmap(
|
||||
D2D1::SizeU(destImageDesc.Width, destImageDesc.Height),
|
||||
(const void*)MappedSurface.pBits,
|
||||
MappedSurface.Pitch,
|
||||
D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)),
|
||||
&ipD2D1SourceBitmap);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// Done with resource
|
||||
hr = ipCopySurface->Unmap();
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Done with resource
|
||||
hr = ipCopySurface->Unmap();
|
||||
CHECK_HR_RETURN(hr);
|
||||
|
||||
// set return value
|
||||
*ppOutBitmap = ipD2D1SourceBitmap.Detach();
|
||||
|
||||
return S_OK;
|
||||
} // CreateBitmap
|
||||
|
||||
static
|
||||
inline
|
||||
COM_DECLSPEC_NOTHROW
|
||||
HRESULT
|
||||
GetContainerFormatByFileName(
|
||||
_In_ LPCWSTR lpcwFileName,
|
||||
_Out_opt_ GUID *pRetVal = NULL
|
||||
)
|
||||
{
|
||||
RESET_POINTER_EX(pRetVal, GUID_NULL);
|
||||
CHECK_POINTER_EX(lpcwFileName, E_INVALIDARG);
|
||||
|
||||
if (lstrlenW(lpcwFileName) == 0) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
LPCWSTR lpcwExtension = ::PathFindExtensionW(lpcwFileName);
|
||||
if (lstrlenW(lpcwExtension) == 0) {
|
||||
return MK_E_INVALIDEXTENSION; // ERROR_MRM_INVALID_FILE_TYPE
|
||||
}
|
||||
|
||||
if (lstrcmpiW(lpcwExtension, L".bmp") == 0)
|
||||
{
|
||||
RESET_POINTER_EX(pRetVal, GUID_ContainerFormatBmp);
|
||||
}
|
||||
else if ((lstrcmpiW(lpcwExtension, L".tif") == 0) ||
|
||||
(lstrcmpiW(lpcwExtension, L".tiff") == 0))
|
||||
{
|
||||
RESET_POINTER_EX(pRetVal, GUID_ContainerFormatTiff);
|
||||
}
|
||||
else if (lstrcmpiW(lpcwExtension, L".png") == 0)
|
||||
{
|
||||
RESET_POINTER_EX(pRetVal, GUID_ContainerFormatPng);
|
||||
}
|
||||
else if ((lstrcmpiW(lpcwExtension, L".jpg") == 0) ||
|
||||
(lstrcmpiW(lpcwExtension, L".jpeg") == 0))
|
||||
{
|
||||
RESET_POINTER_EX(pRetVal, GUID_ContainerFormatJpeg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ERROR_MRM_INVALID_FILE_TYPE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
COM_DECLSPEC_NOTHROW
|
||||
inline
|
||||
HRESULT
|
||||
SaveImageToFile(
|
||||
_In_ IWICImagingFactory *pWICImagingFactory,
|
||||
_In_ IWICBitmapSource *pWICBitmapSource,
|
||||
_In_ LPCWSTR lpcwFileName
|
||||
)
|
||||
{
|
||||
CHECK_POINTER_EX(pWICImagingFactory, E_INVALIDARG);
|
||||
CHECK_POINTER_EX(pWICBitmapSource, E_INVALIDARG);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
GUID guidContainerFormat;
|
||||
|
||||
hr = GetContainerFormatByFileName(lpcwFileName, &guidContainerFormat);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
WICPixelFormatGUID format = GUID_WICPixelFormatDontCare;
|
||||
CComPtr<IWICImagingFactory> ipWICImagingFactory(pWICImagingFactory);
|
||||
CComPtr<IWICBitmapSource> ipWICBitmapSource(pWICBitmapSource);
|
||||
CComPtr<IWICStream> ipStream;
|
||||
CComPtr<IWICBitmapEncoder> ipEncoder;
|
||||
CComPtr<IWICBitmapFrameEncode> ipFrameEncode;
|
||||
unsigned int uiWidth = 0;
|
||||
unsigned int uiHeight = 0;
|
||||
|
||||
hr = ipWICImagingFactory->CreateStream(&ipStream);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = ipStream->InitializeFromFilename(lpcwFileName, GENERIC_WRITE);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = ipWICImagingFactory->CreateEncoder(guidContainerFormat, NULL, &ipEncoder);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipEncoder->Initialize(ipStream, WICBitmapEncoderNoCache);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipEncoder->CreateNewFrame(&ipFrameEncode, NULL);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipFrameEncode->Initialize(NULL);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipWICBitmapSource->GetSize(&uiWidth, &uiHeight);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipFrameEncode->SetSize(uiWidth, uiHeight);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipFrameEncode->SetPixelFormat(&format);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipFrameEncode->WriteSource(ipWICBitmapSource, NULL);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipFrameEncode->Commit();
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ipEncoder->Commit();
|
||||
}
|
||||
|
||||
return hr;
|
||||
} // SaveImageToFile
|
||||
|
||||
}; // end class DXGICaptureHelper
|
||||
|
||||
#endif // __DXGICAPTUREHELPER_H__
|
|
@ -0,0 +1,150 @@
|
|||
/*****************************************************************************
|
||||
* DXGICaptureTypes.h
|
||||
*
|
||||
* Copyright (C) 2020 Gokhan Erdogdu <gokhan_erdogdu - at - yahoo - dot - com>
|
||||
*
|
||||
* DXGICapture is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* DXGICapture is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
******************************************************************************/
|
||||
#pragma once
|
||||
#ifndef __DXGICAPTURETYPES_H__
|
||||
#define __DXGICAPTURETYPES_H__
|
||||
|
||||
#include <dxgi1_2.h>
|
||||
#include <windef.h>
|
||||
#include <sal.h>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// enum tagFrameSizeMode_e
|
||||
//
|
||||
typedef enum tagFrameSizeMode_e : UINT
|
||||
{
|
||||
tagFrameSizeMode_Normal = 0x0,
|
||||
tagFrameSizeMode_StretchImage = 0x1,
|
||||
tagFrameSizeMode_AutoSize = 0x2,
|
||||
tagFrameSizeMode_CenterImage = 0x3,
|
||||
tagFrameSizeMode_Zoom = 0x4,
|
||||
} tagFrameSizeMode;
|
||||
|
||||
//
|
||||
// enum tagFrameRotationMode_e
|
||||
//
|
||||
typedef enum tagFrameRotationMode_e : UINT
|
||||
{
|
||||
tagFrameRotationMode_Auto = 0x0,
|
||||
tagFrameRotationMode_Identity = 0x1,
|
||||
tagFrameRotationMode_90 = 0x2,
|
||||
tagFrameRotationMode_180 = 0x3,
|
||||
tagFrameRotationMode_270 = 0x4,
|
||||
} tagFrameRotationMode;
|
||||
|
||||
//
|
||||
// Holds info about the pointer/cursor
|
||||
// struct tagMouseInfo_s
|
||||
//
|
||||
typedef struct tagMouseInfo_s
|
||||
{
|
||||
UINT ShapeBufferSize;
|
||||
_Field_size_bytes_(ShapeBufferSize) BYTE* PtrShapeBuffer;
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO ShapeInfo;
|
||||
POINT Position;
|
||||
bool Visible;
|
||||
UINT WhoUpdatedPositionLast;
|
||||
LARGE_INTEGER LastTimeStamp;
|
||||
} tagMouseInfo;
|
||||
|
||||
//
|
||||
// struct tagFrameSize_s
|
||||
//
|
||||
typedef struct tagFrameSize_s
|
||||
{
|
||||
LONG Width;
|
||||
LONG Height;
|
||||
} tagFrameSize;
|
||||
|
||||
//
|
||||
// struct tagBounds_s
|
||||
//
|
||||
typedef struct tagFrameBounds_s
|
||||
{
|
||||
LONG X;
|
||||
LONG Y;
|
||||
LONG Width;
|
||||
LONG Height;
|
||||
} tagFrameBounds;
|
||||
|
||||
//
|
||||
// struct tagFrameBufferInfo_s
|
||||
//
|
||||
typedef struct tagFrameBufferInfo_s
|
||||
{
|
||||
UINT BufferSize;
|
||||
_Field_size_bytes_(BufferSize) BYTE* Buffer;
|
||||
INT BytesPerPixel;
|
||||
tagFrameBounds Bounds;
|
||||
INT Pitch;
|
||||
} tagFrameBufferInfo;
|
||||
|
||||
//
|
||||
// struct tagDublicatorMonitorInfo_s
|
||||
//
|
||||
typedef struct tagDublicatorMonitorInfo_s
|
||||
{
|
||||
INT Idx;
|
||||
WCHAR DisplayName[64];
|
||||
INT RotationDegrees;
|
||||
tagFrameBounds Bounds;
|
||||
} tagDublicatorMonitorInfo;
|
||||
|
||||
typedef std::vector<tagDublicatorMonitorInfo*> DublicatorMonitorInfoVec;
|
||||
|
||||
//
|
||||
// struct tagScreenCaptureFilterConfig_s
|
||||
//
|
||||
typedef struct tagScreenCaptureFilterConfig_s
|
||||
{
|
||||
public:
|
||||
INT MonitorIdx;
|
||||
INT ShowCursor;
|
||||
tagFrameRotationMode RotationMode;
|
||||
tagFrameSizeMode SizeMode;
|
||||
tagFrameSize OutputSize; /* Discard for tagFrameSizeMode_AutoSize */
|
||||
} tagScreenCaptureFilterConfig;
|
||||
|
||||
//
|
||||
// struct tagRendererInfo_s
|
||||
//
|
||||
typedef struct tagRendererInfo_s
|
||||
{
|
||||
INT MonitorIdx;
|
||||
INT ShowCursor;
|
||||
tagFrameRotationMode RotationMode;
|
||||
tagFrameSizeMode SizeMode;
|
||||
tagFrameSize OutputSize;
|
||||
|
||||
FLOAT RotationDegrees;
|
||||
FLOAT ScaleX;
|
||||
FLOAT ScaleY;
|
||||
DXGI_FORMAT SrcFormat;
|
||||
tagFrameBounds SrcBounds;
|
||||
tagFrameBounds DstBounds;
|
||||
} tagRendererInfo;
|
||||
|
||||
// macros
|
||||
#define RESET_POINTER_EX(p, v) if (nullptr != (p)) { *(p) = (v); }
|
||||
#define RESET_POINTER(p) RESET_POINTER_EX(p, nullptr)
|
||||
#define CHECK_POINTER_EX(p, hr) if (nullptr == (p)) { return (hr); }
|
||||
#define CHECK_POINTER(p) CHECK_POINTER_EX(p, E_POINTER)
|
||||
#define CHECK_HR_BREAK(hr) if (FAILED(hr)) { break; }
|
||||
#define CHECK_HR_RETURN(hr) { HRESULT hr_379f4648 = hr; if (FAILED(hr_379f4648)) { return hr_379f4648; } }
|
||||
|
||||
#endif // __DXGICAPTURETYPES_H__
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
class Debuger
|
||||
{
|
||||
public:
|
||||
Debuger();
|
||||
~Debuger();
|
||||
static int Debug(wstring log);
|
||||
static int Debug(const wchar_t *format, ...);
|
||||
static int Debug(string log);
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#include "H264Docoder.h"
|
||||
#include "Debuger.h"
|
||||
extern "C" {
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
}
|
||||
|
||||
H264decoder::H264decoder()
|
||||
:mObserver(nullptr){
|
||||
this->mObserverType = Observer_Video;
|
||||
avcodec_register_all();
|
||||
mCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (!mCodec) {
|
||||
cout << "could not found 264 decoder" << endl;
|
||||
exit(1);
|
||||
}
|
||||
mCtx = avcodec_alloc_context3(mCodec);
|
||||
picture = av_frame_alloc();
|
||||
if ((mCodec->capabilities)&AV_CODEC_CAP_TRUNCATED)
|
||||
(mCtx->flags) |= AV_CODEC_FLAG2_CHUNKS;
|
||||
mCtx->height = 720;
|
||||
mCtx->width = 1280;
|
||||
if (avcodec_open2(mCtx, mCodec, NULL) < 0) {
|
||||
cout << "could not open codec\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
H264decoder::~H264decoder()
|
||||
{
|
||||
|
||||
}
|
||||
//
|
||||
// Created by 29019 on 2019/5/7.
|
||||
//
|
||||
const int width = 640;
|
||||
const int height = 480;
|
||||
const int framesize = width * height * 3 / 2; //Ò»¸±Í¼Ëùº¬µÄÏñËظöÊý
|
||||
|
||||
VData *H264decoder::Decodes(void *dat,uint32_t size) {
|
||||
//FILE *pOut = fopen("pic.yuv","wb+");
|
||||
AVPacket pkt;
|
||||
int got_picture = 0;
|
||||
int len = 0;
|
||||
|
||||
picture = av_frame_alloc();
|
||||
av_init_packet(&pkt);
|
||||
|
||||
char* data = (char*)dat;
|
||||
pkt.data = (uint8_t *)data;
|
||||
pkt.size = size;
|
||||
|
||||
len = avcodec_decode_video2(this->mCtx, picture, &got_picture, &pkt);
|
||||
if (len < 0) {
|
||||
printf("Error while decoding a frame.\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (got_picture == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
++frame;
|
||||
AVPixelFormat pix;
|
||||
int pic_size = avpicture_get_size(AV_PIX_FMT_YUVJ420P, picture->width, picture->height);
|
||||
/*
|
||||
cout << "receive width " << picture->width << " height "
|
||||
<< picture->height<<"pic size "
|
||||
<< pic_size <<" channel layout "
|
||||
<< picture->linesize[0]<< endl;*/
|
||||
|
||||
uint32_t pitchY = picture->linesize[0];
|
||||
uint32_t pitchU = picture->linesize[1];
|
||||
uint32_t pitchV = picture->linesize[2];
|
||||
|
||||
uint8_t *avY = picture->data[0];
|
||||
uint8_t *avU = picture->data[1];
|
||||
uint8_t *avV = picture->data[2];
|
||||
if (nullptr != mObserver) {
|
||||
this->mObserver->OnRecieveData(picture);
|
||||
}
|
||||
av_frame_free(&picture);
|
||||
|
||||
}
|
||||
|
||||
void H264decoder::OnRtmpFrame(void * dat, uint32_t size)
|
||||
{
|
||||
//Debuger::Debug(L"get data\r\n");
|
||||
this->Decodes(dat, size);
|
||||
}
|
||||
|
||||
int H264decoder::SetObserver(H264DecodeObserver *p)
|
||||
{
|
||||
if (nullptr != p)
|
||||
this->mObserver = p;
|
||||
else
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
#include <ctype.h>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <time.h>
|
||||
#include "RtmpPuller.h"
|
||||
#include "RtmpPuller2.h"
|
||||
|
||||
using namespace std;
|
||||
extern "C" {
|
||||
#include "libavutil/pixfmt.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "sdl/SDL.h"
|
||||
}
|
||||
#define INBUF_SIZE 4096
|
||||
typedef vector<char> VData;
|
||||
class Decoder {
|
||||
private:
|
||||
list<VData> mDecodeData;
|
||||
public:
|
||||
virtual int Decode(VData &dat) { return -1; };
|
||||
};
|
||||
|
||||
typedef class H264decoder :public Decoder, public RtmpPuller2::RtmpPullObserver {
|
||||
public:
|
||||
class H264DecodeObserver {
|
||||
public:
|
||||
virtual int OnRecieveData(AVFrame *frame) { return 0; };
|
||||
};
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
};
|
||||
H264decoder();
|
||||
~H264decoder();
|
||||
VData *Decodes(void *dat, uint32_t len);
|
||||
void OnRtmpFrame(void * dat, uint32_t size);
|
||||
int SetObserver(H264DecodeObserver *);
|
||||
private:
|
||||
AVCodec *mCodec;
|
||||
AVCodecContext *mCtx = NULL;
|
||||
int frame, got_picture, len;
|
||||
AVFrame *picture;
|
||||
AVPacket avpkt;
|
||||
H264DecodeObserver *mObserver;
|
||||
|
||||
//SDL---------------------------
|
||||
int screen_w = 0, screen_h = 0;
|
||||
SDL_Window *screen;
|
||||
SDL_Renderer* sdlRenderer;
|
||||
SDL_Texture* sdlTexture;
|
||||
SDL_Rect sdlRect;
|
||||
|
||||
|
||||
}CH264Decoder;
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "ImageUtil.h"
|
||||
|
||||
bool GuidCompare(GUID g1, GUID g2) {
|
||||
if (g1.Data1 != g2.Data1) {
|
||||
return false;
|
||||
}
|
||||
if (g1.Data2 != g2.Data2) {
|
||||
return false;
|
||||
}
|
||||
if (g1.Data3 != g2.Data3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AVPixelFormat GetFormatFromGuid(GUID g)
|
||||
{
|
||||
if (GuidCompare(g, MEDIASUBTYPE_YUY2)) {
|
||||
return AV_PIX_FMT_YUYV422;
|
||||
}
|
||||
if (GuidCompare(g, MEDIASUBTYPE_RGB24)) {
|
||||
return AV_PIX_FMT_RGB24;
|
||||
}
|
||||
if (GuidCompare(g, MEDIASUBTYPE_RGB32)) {
|
||||
return AV_PIX_FMT_RGB32;
|
||||
}
|
||||
if (GuidCompare(g, MEDIASUBTYPE_MJPG)) {
|
||||
return AV_PIX_FMT_YUVJ420P;
|
||||
}
|
||||
if (GuidCompare(g, MEDIASUBTYPE_IYUV)) {
|
||||
return AV_PIX_FMT_YUYV422;
|
||||
}
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
#include "guiddef.h"
|
||||
#include "uuids.h"
|
||||
extern "C" {
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
}
|
||||
|
||||
AVPixelFormat GetFormatFromGuid(GUID g);
|
|
@ -0,0 +1,132 @@
|
|||
#include "RtmpPuller.h"
|
||||
|
||||
|
||||
RtmpPuller::RtmpPuller()
|
||||
:mFrameIndex(0), mAudioIndex(-1),mVideoIndex(-1)
|
||||
, mAudioStream(nullptr),mVideoStream(nullptr)
|
||||
{
|
||||
av_register_all();
|
||||
//Network
|
||||
avformat_network_init();
|
||||
//Input
|
||||
}
|
||||
|
||||
int RtmpPuller::ConnectServer(const char *p)
|
||||
{
|
||||
int ret = 0;
|
||||
if ((ret = avformat_open_input(&mIfmtCtx, p, 0, 0)) < 0) {
|
||||
printf("Could not open input file.");
|
||||
return -1;
|
||||
}
|
||||
if ((ret = avformat_find_stream_info(mIfmtCtx, 0)) < 0) {
|
||||
printf("Failed to retrieve input stream information");
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < mIfmtCtx->nb_streams; i++) {
|
||||
if (mIfmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
mVideoIndex = i;
|
||||
}
|
||||
if (mIfmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
mAudioIndex = i;
|
||||
}
|
||||
}
|
||||
if(mAudioIndex > -1)
|
||||
this->mAudioStream = mIfmtCtx->streams[mAudioIndex];
|
||||
if(mVideoIndex > -1)
|
||||
this->mVideoStream = mIfmtCtx->streams[mVideoIndex];
|
||||
av_dump_format(mIfmtCtx, 0, p, 0);
|
||||
mH264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
|
||||
mStatus = RUNNING;
|
||||
if((mAudioIndex == -1 ) &&(mVideoIndex == -1))
|
||||
mStatus = NOSOURCE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ThreadPull(RtmpPuller*p) {
|
||||
while (p->Status() == RtmpPuller::CAP_STATUS::RUNNING) {
|
||||
p->PullData();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RtmpPuller::StartPull()
|
||||
{
|
||||
this->mThread = new std::thread(ThreadPull, this);
|
||||
this->mThread->get_id();
|
||||
mStatus = RUNNING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RtmpPuller::PullData()
|
||||
{
|
||||
static int drop = 0;
|
||||
AVStream *in_stream;
|
||||
//Get an AVPacket
|
||||
int ret = av_read_frame(mIfmtCtx, &pkt);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
in_stream = mIfmtCtx->streams[pkt.stream_index];
|
||||
/* copy packet */
|
||||
//Convert PTS/DTS
|
||||
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, in_stream->time_base,
|
||||
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, in_stream->time_base,
|
||||
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, in_stream->time_base);
|
||||
pkt.pos = -1;
|
||||
//Print to Screen
|
||||
if (drop < 100) {
|
||||
drop++;
|
||||
goto end;
|
||||
}
|
||||
if (pkt.stream_index == mVideoIndex) {
|
||||
printf("Receive %8d video frames from input URL\n", mFrameIndex);
|
||||
mFrameIndex++;
|
||||
av_bitstream_filter_filter(mH264bsfc, in_stream->codec, NULL,
|
||||
&pkt.data, &pkt.size, pkt.data, pkt.size, 0);
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Video) {
|
||||
p->OnRtmpFrame(pkt.data, pkt.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pkt.stream_index == mAudioIndex) {
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Audio) {
|
||||
p->OnRtmpFrame(pkt.data, pkt.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end:
|
||||
//printf("%02x %02x %02x %02x %02x\r\n", pkt.data[0], pkt.data[1], pkt.data[2], pkt.data[3], pkt.data[4]);
|
||||
av_free_packet(&pkt);
|
||||
}
|
||||
|
||||
int RtmpPuller::SetObserver(RtmpPullObserver *p)
|
||||
{
|
||||
if (nullptr == p)
|
||||
return -1;
|
||||
mMux.lock();
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
if (p == *itr) return 0;
|
||||
}
|
||||
this->mObserver.push_back(p);
|
||||
mMux.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
RtmpPuller::CAP_STATUS RtmpPuller::Status()
|
||||
{
|
||||
return this->mStatus;
|
||||
}
|
||||
|
||||
AVStream * RtmpPuller::AudioStream()
|
||||
{
|
||||
return this->mAudioStream;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
//Windows
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
using namespace std;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/time.h"
|
||||
};
|
||||
|
||||
#pragma comment (lib, "ws2_32.lib")
|
||||
#pragma comment (lib, "Secur32.lib")
|
||||
#pragma comment (lib, "Bcrypt.lib")
|
||||
|
||||
class RtmpPuller {
|
||||
public:
|
||||
class RtmpPullObserver {
|
||||
public :
|
||||
enum ObserverType {
|
||||
Observer_Video = 0,
|
||||
Observer_Audio = 1,
|
||||
};
|
||||
virtual void OnRtmpFrame(void * dat, uint32_t size) {};
|
||||
ObserverType mObserverType;
|
||||
};
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
NOSOURCE = 6,
|
||||
};
|
||||
RtmpPuller();
|
||||
int ConnectServer(const char *);
|
||||
int StartPull();
|
||||
int PullData();
|
||||
int SetObserver(RtmpPullObserver *);
|
||||
CAP_STATUS Status();
|
||||
AVStream *AudioStream();
|
||||
private:
|
||||
CAP_STATUS mStatus;
|
||||
AVOutputFormat *mOutFormat = NULL;
|
||||
//Input AVFormatContext and Output AVFormatContext
|
||||
AVFormatContext *mIfmtCtx = NULL;
|
||||
AVPacket pkt;
|
||||
string mRtmpUrl;
|
||||
int mVideoIndex;
|
||||
int mAudioIndex;
|
||||
|
||||
int mFrameIndex;
|
||||
AVBitStreamFilterContext* mH264bsfc;
|
||||
std::thread *mThread;
|
||||
vector<RtmpPullObserver*> mObserver;
|
||||
AVStream *mAudioStream;
|
||||
AVStream *mVideoStream;
|
||||
mutex mMux;
|
||||
};
|
||||
|
||||
int ThreadPull(RtmpPuller*p);
|
|
@ -0,0 +1,251 @@
|
|||
#include "RtmpPuller2.h"
|
||||
#include "Debuger.h"
|
||||
RtmpPuller2::RtmpPuller2()
|
||||
{
|
||||
|
||||
mAccBuffer = new uint8_t[3000];
|
||||
}
|
||||
RtmpPuller2::~RtmpPuller2()
|
||||
{
|
||||
}
|
||||
|
||||
int ThreadPull(RtmpPuller2*p) {
|
||||
while (p->Status() == RtmpPuller2::CAP_STATUS::RUNNING) {
|
||||
p->PullData();
|
||||
Sleep(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// 关闭拉流
|
||||
int RtmpPuller2::StopPull()
|
||||
{
|
||||
mStatus = STOP;
|
||||
this->mThread->join();
|
||||
RTMP_Close(mRtmp);
|
||||
RTMP_Free(mRtmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RtmpPuller2::StartPull()
|
||||
{
|
||||
if(this->mStatus == CONNECTED) {
|
||||
mStatus = RUNNING;
|
||||
this->mThread = new std::thread(ThreadPull, this);
|
||||
this->mThread->get_id();
|
||||
}
|
||||
else {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FILE *fp = nullptr;
|
||||
int RtmpPuller2::PullData()
|
||||
{
|
||||
RTMPPacket packet = { 0 };
|
||||
// Parse rtmp stream to h264 and aac
|
||||
uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 };
|
||||
int ret = RTMP_ReadPacket(mRtmp, &packet);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (nullptr == fp) {
|
||||
fp = fopen("src.aac", "wb");
|
||||
}
|
||||
if (RTMPPacket_IsReady(&packet)) {
|
||||
// Process packet, eg: set chunk size, set bw, ...
|
||||
RTMP_ClientPacket(mRtmp, &packet);
|
||||
if (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) {
|
||||
bool keyframe = 0x17 == packet.m_body[0] ? true : false;
|
||||
bool sequence = 0x00 == packet.m_body[1];
|
||||
printf("keyframe=%s, sequence=%s\n", keyframe ? "true" : "false", sequence ? "true" : "false");
|
||||
// SPS/PPS sequence
|
||||
if (sequence) {
|
||||
uint32_t offset = 10;
|
||||
uint32_t sps_num = packet.m_body[offset++] & 0x1f;
|
||||
for (int i = 0; i < sps_num; i++) {
|
||||
uint8_t ch0 = packet.m_body[offset];
|
||||
uint8_t ch1 = packet.m_body[offset + 1];
|
||||
uint32_t sps_len = ((ch0 << 8) | ch1);
|
||||
offset += 2;
|
||||
packet.m_body[offset - 1] = 0x01;
|
||||
packet.m_body[offset - 2] = 0x00;
|
||||
packet.m_body[offset - 3] = 0x00;
|
||||
packet.m_body[offset - 4] = 0x00;
|
||||
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Video) {
|
||||
p->OnRtmpFrame(packet.m_body + offset - 4, sps_len + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write sps data
|
||||
//fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
|
||||
//fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, _file_ptr);
|
||||
offset += sps_len;
|
||||
}
|
||||
uint32_t pps_num = packet.m_body[offset++] & 0x1f;
|
||||
for (int i = 0; i < pps_num; i++) {
|
||||
uint8_t ch0 = packet.m_body[offset];
|
||||
uint8_t ch1 = packet.m_body[offset + 1];
|
||||
uint32_t pps_len = ((ch0 << 8) | ch1);
|
||||
offset += 2;
|
||||
packet.m_body[offset - 1] = 0x01;
|
||||
packet.m_body[offset - 2] = 0x00;
|
||||
packet.m_body[offset - 3] = 0x00;
|
||||
packet.m_body[offset - 4] = 0x00;
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Video) {
|
||||
p->OnRtmpFrame(packet.m_body + offset - 4, pps_len + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write pps data
|
||||
offset += pps_len;
|
||||
}
|
||||
}
|
||||
// Nalu frames
|
||||
else {
|
||||
uint32_t offset = 5;
|
||||
uint8_t ch0 = packet.m_body[offset];
|
||||
uint8_t ch1 = packet.m_body[offset + 1];
|
||||
uint8_t ch2 = packet.m_body[offset + 2];
|
||||
uint8_t ch3 = packet.m_body[offset + 3];
|
||||
uint32_t data_len = ((ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3);
|
||||
offset += 4;
|
||||
packet.m_body[offset - 1] = 0x01;
|
||||
packet.m_body[offset - 2] = 0x00;
|
||||
packet.m_body[offset - 3] = 0x00;
|
||||
packet.m_body[offset - 4] = 0x00;
|
||||
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Video) {
|
||||
p->OnRtmpFrame(packet.m_body + offset - 4, data_len + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write nalu data(already started with '0x00,0x00,0x00,0x01')
|
||||
//fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
|
||||
offset += data_len;
|
||||
}
|
||||
}
|
||||
else if (packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) {
|
||||
bool sequence = 0x00 == packet.m_body[1];
|
||||
printf("sequence=%s\n", sequence ? "true" : "false");
|
||||
// AAC sequence
|
||||
if (sequence) {
|
||||
uint8_t format = (packet.m_body[0] & 0xf0) >> 4;
|
||||
uint8_t samplerate = (packet.m_body[0] & 0x0c) >> 2;
|
||||
uint8_t sampledepth = (packet.m_body[0] & 0x02) >> 1;
|
||||
uint8_t type = packet.m_body[0] & 0x01;
|
||||
// sequence = packet.m_body[1];
|
||||
// AAC(AudioSpecificConfig)
|
||||
if (format == 10) {
|
||||
ch0 = packet.m_body[2];
|
||||
ch1 = packet.m_body[3];
|
||||
config = ((ch0 << 8) | ch1);
|
||||
object_type = (config & 0xF800) >> 11;
|
||||
sample_frequency_index = (config & 0x0780) >> 7;
|
||||
channels = (config & 0x78) >> 3;
|
||||
frame_length_flag = (config & 0x04) >> 2;
|
||||
depend_on_core_coder = (config & 0x02) >> 1;
|
||||
extension_flag = config & 0x01;
|
||||
}
|
||||
// Speex(Fix data here, so no need to parse...)
|
||||
else if (format == 11) {
|
||||
// 16 KHz, mono, 16bit/sample
|
||||
type = 0;
|
||||
sampledepth = 1;
|
||||
samplerate = 4;
|
||||
}
|
||||
}
|
||||
// Audio frames
|
||||
else {
|
||||
// ADTS(7 bytes) + AAC data
|
||||
uint32_t data_len = packet.m_nBodySize - 2 + 7;
|
||||
uint8_t adts[7];
|
||||
adts[0] = 0xff;
|
||||
adts[1] = 0xf1;
|
||||
adts[2] = ((object_type - 1) << 6) | (sample_frequency_index << 2)
|
||||
| (channels >> 2);
|
||||
adts[3] = ((channels & 3) << 6) + (data_len >> 11);
|
||||
adts[4] = (data_len & 0x7FF) >> 3;
|
||||
adts[5] = ((data_len & 7) << 5) + 0x1F;
|
||||
adts[6] = 0xfc;
|
||||
// Write audio frames
|
||||
fwrite(adts, sizeof(uint8_t), 7, fp);
|
||||
fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, fp);
|
||||
fflush(fp);
|
||||
memcpy(mAccBuffer, adts, 7);
|
||||
memcpy(mAccBuffer + 7, packet.m_body + 2, packet.m_nBodySize - 2);
|
||||
|
||||
if (mObserver.size() > 0) {
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
RtmpPullObserver *p = (RtmpPullObserver *)*itr;
|
||||
if (p->mObserverType == RtmpPullObserver::Observer_Audio) {
|
||||
p->OnRtmpFrame(mAccBuffer, packet.m_nBodySize - 2 + 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RTMPPacket_Free(&packet);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RtmpPuller2::SetObserver(RtmpPuller2::RtmpPullObserver *p)
|
||||
{
|
||||
if (nullptr == p)
|
||||
return -1;
|
||||
mMux.lock();
|
||||
for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) {
|
||||
if (p == *itr) return 0;
|
||||
}
|
||||
this->mObserver.push_back(p);
|
||||
mMux.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
RtmpPuller2::CAP_STATUS RtmpPuller2::Status()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
|
||||
int RtmpPuller2::ConnectServer(string url)
|
||||
{
|
||||
mRtmp = RTMP_Alloc();
|
||||
RTMP_Init(mRtmp);
|
||||
if (RTMP_SetupURL(mRtmp, (char*)url.c_str()) == FALSE)
|
||||
{
|
||||
RTMP_Free(mRtmp);
|
||||
mStatus = FAIL;
|
||||
return -1;
|
||||
}
|
||||
/*连接服务器*/
|
||||
if (RTMP_Connect(mRtmp, NULL) == FALSE)
|
||||
{
|
||||
RTMP_Free(mRtmp);
|
||||
mStatus = FAIL;
|
||||
return -1;
|
||||
}
|
||||
/*连接流*/
|
||||
if (RTMP_ConnectStream(mRtmp, 0) == FALSE)
|
||||
{
|
||||
RTMP_Close(mRtmp);
|
||||
RTMP_Free(mRtmp);
|
||||
mStatus = FAIL;
|
||||
return -1;
|
||||
}
|
||||
mStatus = CONNECTED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "librtmp\rtmp.h"
|
||||
#include "librtmp\rtmp_sys.h"
|
||||
#include "librtmp\amf.h"
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
using namespace std;
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#pragma comment(lib,"WS2_32.lib")
|
||||
#pragma comment(lib,"winmm.lib")
|
||||
#endif
|
||||
|
||||
class RtmpPuller2
|
||||
{
|
||||
public:
|
||||
class RtmpPullObserver {
|
||||
public:
|
||||
enum ObserverType {
|
||||
Observer_Video = 0,
|
||||
Observer_Audio = 1,
|
||||
};
|
||||
virtual void OnRtmpFrame(void * dat, uint32_t size) {};
|
||||
ObserverType mObserverType;
|
||||
};
|
||||
enum CAP_STATUS {
|
||||
CONNECTED = 0,
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
NOSOURCE = 6,
|
||||
};
|
||||
RtmpPuller2();
|
||||
~RtmpPuller2();
|
||||
|
||||
int StopPull();
|
||||
int StartPull();
|
||||
int PullData();
|
||||
int SetObserver(RtmpPuller2::RtmpPullObserver *);
|
||||
CAP_STATUS Status();
|
||||
|
||||
int ConnectServer(string url);
|
||||
private:
|
||||
std::thread *mThread;
|
||||
RTMP *mRtmp;
|
||||
string mUrl;
|
||||
CAP_STATUS mStatus;
|
||||
vector<RtmpPuller2::RtmpPullObserver*> mObserver;
|
||||
mutex mMux;
|
||||
uint8_t *mAccBuffer;
|
||||
// adts 头部信息,因为aac码流只会在首包发送
|
||||
uint8_t ch0 = 0;
|
||||
uint8_t ch1 = 0;
|
||||
uint16_t config = 0;
|
||||
uint16_t object_type = 0;
|
||||
uint16_t sample_frequency_index = 0;
|
||||
uint16_t channels = 0;
|
||||
uint16_t frame_length_flag = 0;
|
||||
uint16_t depend_on_core_coder = 0;
|
||||
uint16_t extension_flag = 0;
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,573 @@
|
|||
#include "RtmpPusher.h"
|
||||
|
||||
/**
|
||||
* 初始化winsock
|
||||
*
|
||||
* @成功则返回1 , 失败则返回相应错误代码
|
||||
*/
|
||||
int InitSockets()
|
||||
{
|
||||
#ifdef WIN32
|
||||
WORD version;
|
||||
WSADATA wsaData;
|
||||
version = MAKEWORD(1, 1);
|
||||
return (WSAStartup(version, &wsaData) == 0);
|
||||
#else
|
||||
return TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RtmpPusher::IfConnect()
|
||||
{
|
||||
return mIfConnected;
|
||||
}
|
||||
|
||||
int RtmpPusher::RTMP264_Connect(const char* url)
|
||||
{
|
||||
InitSockets();
|
||||
m_pRtmp = RTMP_Alloc();
|
||||
RTMP_Init(m_pRtmp);
|
||||
/*设置URL*/
|
||||
if (RTMP_SetupURL(m_pRtmp, (char*)url) == FALSE)
|
||||
{
|
||||
RTMP_Free(m_pRtmp);
|
||||
return -1;
|
||||
}
|
||||
/*设置可写,即发布流,这个函数必须在连接前使用,否则无效*/
|
||||
RTMP_EnableWrite(m_pRtmp);
|
||||
/*连接服务器*/
|
||||
if (RTMP_Connect(m_pRtmp, NULL) == FALSE)
|
||||
{
|
||||
RTMP_Free(m_pRtmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*连接流*/
|
||||
if (RTMP_ConnectStream(m_pRtmp, 0) == FALSE)
|
||||
{
|
||||
RTMP_Close(m_pRtmp);
|
||||
RTMP_Free(m_pRtmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
this->mUrl = string(url);
|
||||
this->mIfConnected = true;
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* 释放winsock
|
||||
*
|
||||
* @成功则返回0 , 失败则返回相应错误代码
|
||||
*/
|
||||
inline void CleanupSockets()
|
||||
{
|
||||
#ifdef WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
void RtmpPusher::RTMP264_Close()
|
||||
{
|
||||
mMux.lock();
|
||||
if (m_pRtmp)
|
||||
{
|
||||
RTMP_Close(m_pRtmp);
|
||||
RTMP_Free(m_pRtmp);
|
||||
m_pRtmp = NULL;
|
||||
}
|
||||
mMux.unlock();
|
||||
CleanupSockets();
|
||||
}
|
||||
RTMPPacket* gPacket = nullptr;
|
||||
|
||||
int RtmpPusher::SendPacket(unsigned int nPacketType, unsigned char * data,
|
||||
unsigned int size, unsigned int nTimestamp)
|
||||
{
|
||||
static bool once = true;
|
||||
/*分配包内存和初始化,len为包体长度*/
|
||||
if(nullptr == gPacket)
|
||||
gPacket = (RTMPPacket *)malloc(640*720*3 + size);
|
||||
memset(gPacket, 0, RTMP_HEAD_SIZE);
|
||||
/*包体内存*/
|
||||
|
||||
gPacket->m_body = (char *)gPacket + RTMP_HEAD_SIZE;
|
||||
gPacket->m_nBodySize = size;
|
||||
memcpy(gPacket->m_body, data, size);
|
||||
|
||||
gPacket->m_hasAbsTimestamp = 0;
|
||||
gPacket->m_packetType = nPacketType; /*此处为类型有两种一种是音频,一种是视频*/
|
||||
gPacket->m_nInfoField2 = m_pRtmp->m_stream_id;
|
||||
gPacket->m_nChannel = 0x04;
|
||||
|
||||
|
||||
gPacket->m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size != 4)
|
||||
{
|
||||
gPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
}
|
||||
|
||||
gPacket->m_nTimeStamp = nTimestamp;
|
||||
/*发送*/
|
||||
int nRet = 0;
|
||||
if (RTMP_IsConnected(m_pRtmp))
|
||||
{
|
||||
nRet = RTMP_SendPacket(m_pRtmp, gPacket, FALSE); /*TRUE为放进发送队列,FALSE是不放进发送队列,直接发送*/
|
||||
}
|
||||
else {
|
||||
if (once) {
|
||||
once = false;
|
||||
}
|
||||
}
|
||||
/*释放内存*/
|
||||
//free(gPacket);
|
||||
return nRet;
|
||||
}
|
||||
|
||||
int RtmpPusher::SendVideoPacket(unsigned int nPacketType,
|
||||
unsigned char * data, unsigned int size, unsigned int nTimestamp)
|
||||
{
|
||||
|
||||
RTMPPacket* packet;
|
||||
/*分配包内存和初始化,len为包体长度*/
|
||||
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + size);
|
||||
memset(packet, 0, RTMP_HEAD_SIZE);
|
||||
/*包体内存*/
|
||||
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
|
||||
packet->m_nBodySize = size;
|
||||
memcpy(packet->m_body, data, size);
|
||||
packet->m_hasAbsTimestamp = 0;
|
||||
packet->m_packetType = nPacketType; /*此处为类型有两种一种是音频,一种是视频*/
|
||||
packet->m_nInfoField2 = m_pRtmp->m_stream_id;
|
||||
packet->m_nChannel = 0x04;
|
||||
packet->m_nTimeStamp += 33;
|
||||
|
||||
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size != 4)
|
||||
{
|
||||
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
}
|
||||
packet->m_nTimeStamp = nTimestamp;
|
||||
/*发送*/
|
||||
int nRet = 0;
|
||||
if (RTMP_IsConnected(m_pRtmp))
|
||||
{
|
||||
nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE); /*TRUE为放进发送队列,FALSE是不放进发送队列,直接发送*/
|
||||
}
|
||||
/*释放内存*/
|
||||
free(packet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
RtmpPusher::RtmpPusher()
|
||||
:mThread(nullptr),
|
||||
mIfConnected(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
RtmpPusher::~RtmpPusher()
|
||||
{
|
||||
if (m_pRtmp)
|
||||
{
|
||||
RTMP_Close(m_pRtmp);
|
||||
RTMP_Free(m_pRtmp);
|
||||
m_pRtmp = NULL;
|
||||
}
|
||||
CleanupSockets();
|
||||
}
|
||||
|
||||
void H264RtmpPuser::OnAudioEncode(const void * frameaddress, uint32_t framelen,uint16_t pts)
|
||||
{
|
||||
uint8_t *pack = (uint8_t*)malloc(framelen);
|
||||
memcpy(pack, frameaddress, framelen);
|
||||
|
||||
mMux.lock();
|
||||
Buffer buf;
|
||||
buf.buf = (uint8_t *)pack;
|
||||
buf.len = framelen;
|
||||
buf.type = PAYLOAD_TYPE_AUDIO;
|
||||
this->mPack.push(buf);
|
||||
mMux.unlock();
|
||||
this->mAudioPts = pts;
|
||||
}
|
||||
|
||||
H264RtmpPuser::H264RtmpPuser()
|
||||
{
|
||||
this->metaData.Pps = nullptr;
|
||||
this->metaData.Sps = nullptr;
|
||||
this->metaData.nPpsLen = 0;
|
||||
this->metaData.nSpsLen = 0;
|
||||
this->mStartTime = 0;
|
||||
mFirtACC = false;
|
||||
}
|
||||
|
||||
int H264RtmpPuser::sortAndSendNal(uint8_t * data, int len)
|
||||
{
|
||||
int i = 0;
|
||||
uint8_t * nalhead = nullptr;
|
||||
uint8_t * naltail = nullptr;
|
||||
uint32_t size = 0;
|
||||
if(0 == mStartTime){
|
||||
mStartTime = RTMP_GetTime();
|
||||
}
|
||||
if (nullptr == data) {
|
||||
return -1;
|
||||
}
|
||||
while (i < len)
|
||||
{
|
||||
// sps pps p frame
|
||||
if ((data[i] == 0x00) && (data[i + 1] == 0x00)
|
||||
&& ((data[i + 2] == 0x00) && (data[i + 3] == 0x01) || (data[i + 2] == 0x01))) {
|
||||
if ((nalhead == nullptr) && (i == 0) ) {
|
||||
if ((data[i + 3] == 0x01) && (data[i + 4] == 0x41)) { //p 帧直接发
|
||||
|
||||
nalhead = data;
|
||||
naltail = data + (len);
|
||||
size = naltail - nalhead;
|
||||
this->SendH264Packet(nalhead, size, 0, RTMP_GetTime() - mStartTime);
|
||||
return 0;
|
||||
}
|
||||
//sps 帧进行解包
|
||||
if ((data[i + 3] == 0x01) && (data[i + 4] == 0x67)) { // sps or pps or sei
|
||||
nalhead = data;
|
||||
i += 1;
|
||||
}
|
||||
//sei
|
||||
if ((data[i + 2] == 0x01) && (data[i + 3] == 0x06)) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// i frame
|
||||
if ((data[i + 2] == 0x01) && (data[i + 3] == 0x65)) {
|
||||
|
||||
naltail = data + i;
|
||||
size = naltail - nalhead;
|
||||
this->SendH264Packet(nalhead, size, 0, RTMP_GetTime() - mStartTime);
|
||||
nalhead = data + i;
|
||||
naltail = data + (len);
|
||||
size = naltail - nalhead;
|
||||
this->SendH264Packet(nalhead, size, 0, RTMP_GetTime() - mStartTime);
|
||||
return 0;
|
||||
}
|
||||
//pps
|
||||
if ((data[i + 3] == 0x01) && (data[i + 4] == 0x68)) { // sps or pps or sei
|
||||
naltail = data + i;
|
||||
size = naltail - nalhead;
|
||||
this->SendH264Packet(nalhead, size, 0, RTMP_GetTime() - mStartTime);
|
||||
nalhead = data + i;
|
||||
i += 3;
|
||||
}//sps
|
||||
if ((data[i + 3] == 0x01) && (data[i + 4] == 0x67)) { // sps or pps or sei
|
||||
nalhead = data + i;
|
||||
i += 3;
|
||||
}
|
||||
//sei
|
||||
if ((data[i + 3] == 0x01) && (data[i + 4] == 0x06)) { // sps or pps or sei
|
||||
naltail = data + i;
|
||||
size = naltail - nalhead;
|
||||
this->SendH264Packet(nalhead, size, 0, RTMP_GetTime() - mStartTime);
|
||||
nalhead = data + i;
|
||||
i += 3;
|
||||
}
|
||||
// sps pps or sei
|
||||
}
|
||||
// 跳过00 00 00 00 01的情况
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 视频同步包,详细结构请见https://blog.csdn.net/liwf616/article/details/51596373
|
||||
int H264RtmpPuser::SendVideoSpsPps(unsigned char * pps,
|
||||
int pps_len, unsigned char * sps,
|
||||
int sps_len,unsigned int nTimeStamp)
|
||||
{
|
||||
RTMPPacket * packet = NULL;//rtmp包结构
|
||||
unsigned char * body = NULL;
|
||||
int i;
|
||||
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024);
|
||||
//RTMPPacket_Reset(packet);//重置packet状态
|
||||
memset(packet, 0, RTMP_HEAD_SIZE + 1024);
|
||||
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
|
||||
body = (unsigned char *)packet->m_body;
|
||||
i = 0;
|
||||
// FrameType == 1,CodecID == 7,
|
||||
body[i++] = 0x17;
|
||||
//AVCPacketType
|
||||
body[i++] = 0x00;
|
||||
|
||||
//CompositionTime
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
|
||||
/*AVCDecoderConfigurationRecord*/
|
||||
body[i++] = 0x01;
|
||||
body[i++] = sps[1];
|
||||
body[i++] = sps[2];
|
||||
body[i++] = sps[3];
|
||||
body[i++] = 0xff;
|
||||
|
||||
/*sps*/
|
||||
body[i++] = 0xe1;
|
||||
body[i++] = (sps_len >> 8) & 0xff;
|
||||
body[i++] = sps_len & 0xff;
|
||||
memcpy(&body[i], sps, sps_len);
|
||||
i += sps_len;
|
||||
|
||||
/*pps*/
|
||||
body[i++] = 0x01;
|
||||
body[i++] = (pps_len >> 8) & 0xff;
|
||||
body[i++] = (pps_len) & 0xff;
|
||||
memcpy(&body[i], pps, pps_len);
|
||||
i += pps_len;
|
||||
|
||||
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
|
||||
packet->m_nBodySize = i;
|
||||
packet->m_nChannel = 0x04;
|
||||
packet->m_nTimeStamp = nTimeStamp;
|
||||
packet->m_hasAbsTimestamp = 0;
|
||||
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
packet->m_nInfoField2 = m_pRtmp->m_stream_id;
|
||||
|
||||
/*调用发送接口*/
|
||||
int nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE);
|
||||
free(packet); //释放内存
|
||||
return nRet;
|
||||
}
|
||||
|
||||
int H264RtmpPuser::SendAudioData(unsigned char * dat,
|
||||
unsigned int size, unsigned int nTimeStamp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int H264RtmpPuser::SendH264Packet(unsigned char * data,
|
||||
unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp)
|
||||
{
|
||||
if(data == NULL){
|
||||
return false;
|
||||
}
|
||||
unsigned int nal_type = 0;
|
||||
// 小帧应该是PPS或者SPS
|
||||
if ((data[0] != 0x00) || (data[1] != 0x00)
|
||||
|| ((data[2] != 0x00)&&data[2]!= 0x01)) {
|
||||
return false;
|
||||
}
|
||||
//Debuger::Debug(L"%02x %02x %02x %02x %02x %02d\r\n",
|
||||
// data[0],data[1],data[2],data[3],data[4],size);
|
||||
if (data[2] == 0x01) {
|
||||
nal_type = data[3];
|
||||
}
|
||||
if (data[3] == 0x01) {
|
||||
nal_type = data[4];
|
||||
}
|
||||
switch (nal_type)
|
||||
{
|
||||
case 0x67: //just update sps and pps
|
||||
if (NULL == metaData.Sps)
|
||||
metaData.Sps = (unsigned char *)malloc(size - 4);
|
||||
h264_decode_sps(data + 4, size - 4, metaData.nWidth, metaData.nHeight, metaData.nFrameRate);
|
||||
metaData.nSpsLen = size - 4;
|
||||
memcpy(this->metaData.Sps, data + 4, size - 4);
|
||||
break;
|
||||
case 0x68: //just update sps and pps
|
||||
this->metaData.nPpsLen = size - 4;
|
||||
if (NULL == metaData.Pps) metaData.Pps = (unsigned char *)malloc(size - 4);
|
||||
memcpy(this->metaData.Pps, data + 4, size - 4);
|
||||
break;
|
||||
case 0x41: //p frame
|
||||
this->sendDataPackH264(data + 4, size - 4, 0, nTimeStamp);
|
||||
break;
|
||||
case 0x65: //i frame
|
||||
this->sendDataPackH264(data + 3, size - 3, 1, nTimeStamp);
|
||||
break;
|
||||
case 0x06:
|
||||
size = size;
|
||||
//this->sendDataPack(data + 4, size - 4, 0, nTimeStamp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
unsigned char *gBody = nullptr;
|
||||
int H264RtmpPuser::sendDataPackH264(unsigned char * data,
|
||||
unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp)
|
||||
{
|
||||
if (gBody == nullptr) {
|
||||
gBody = new unsigned char[640*720*3 + 9];
|
||||
}
|
||||
if (size < 0) {
|
||||
gBody = gBody;
|
||||
}
|
||||
memset(gBody, 0, size + 9);
|
||||
int i = 0;
|
||||
if (1 == bIsKeyFrame) {
|
||||
gBody[i++] = 0x17;// 1:Iframe 7:AVC
|
||||
gBody[i++] = 0x01;// AVC NALU
|
||||
gBody[i++] = 0x00;
|
||||
gBody[i++] = 0x00;
|
||||
gBody[i++] = 0x00;
|
||||
|
||||
// NALU size
|
||||
gBody[i++] = size >> 24 & 0xff;
|
||||
gBody[i++] = size >> 16 & 0xff;
|
||||
gBody[i++] = size >> 8 & 0xff;
|
||||
gBody[i++] = size & 0xff;
|
||||
// NALU data
|
||||
memcpy(&gBody[i], data, size);
|
||||
if(metaData.Sps != nullptr)
|
||||
SendVideoSpsPps(metaData.Pps, metaData.nPpsLen, metaData.Sps,
|
||||
metaData.nSpsLen, 0);
|
||||
}
|
||||
else {
|
||||
gBody[i++] = 0x27;// 2:Pframe 7:AVC
|
||||
gBody[i++] = 0x01;// AVC NALU
|
||||
gBody[i++] = 0x00;
|
||||
gBody[i++] = 0x00;
|
||||
gBody[i++] = 0x00;
|
||||
// NALU size
|
||||
gBody[i++] = size >> 24 & 0xff;
|
||||
gBody[i++] = size >> 16 & 0xff;
|
||||
gBody[i++] = size >> 8 & 0xff;
|
||||
gBody[i++] = size & 0xff;
|
||||
// NALU data
|
||||
|
||||
memcpy(&gBody[i], data, size);
|
||||
}
|
||||
int bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO, gBody, i + size, nTimeStamp);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
int H264RtmpPuser::SendAudioSync(int audioType,
|
||||
int sampleIndex, int channel, unsigned int nTimeStamp)
|
||||
{
|
||||
RTMPPacket * packet = NULL;//rtmp包结构
|
||||
unsigned char * body = NULL;
|
||||
int i;
|
||||
|
||||
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024);
|
||||
//RTMPPacket_Reset(packet);//重置packet状态
|
||||
memset(packet, 0, RTMP_HEAD_SIZE + 1024);
|
||||
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
|
||||
body = (unsigned char *)packet->m_body;
|
||||
|
||||
body[0] = 0xaf;
|
||||
body[1] = 0x00;
|
||||
|
||||
uint16_t audioSpecConf = 0;
|
||||
audioSpecConf |= ((2 << 11) & 0xf800); //2: AACLC
|
||||
audioSpecConf |= ((4 << 7) & 0x0780); //4: 44khz
|
||||
audioSpecConf |= ((2 << 3) & 0x78); //4: 2:stero
|
||||
audioSpecConf |= 0 & 0x07; //4: 0 padding
|
||||
body[2] = (audioSpecConf >> 8) & 0xff;
|
||||
body[3] = audioSpecConf & 0xff;
|
||||
|
||||
|
||||
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
|
||||
packet->m_nBodySize = 4;
|
||||
packet->m_nChannel = 0x04;
|
||||
packet->m_nTimeStamp = nTimeStamp;
|
||||
packet->m_hasAbsTimestamp = 0;
|
||||
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
packet->m_nInfoField2 = m_pRtmp->m_stream_id;
|
||||
|
||||
/*调用发送接口*/
|
||||
int nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE);
|
||||
free(packet); //释放内存
|
||||
return nRet;
|
||||
}
|
||||
|
||||
|
||||
int H264RtmpPuser::sendDataPackAAC(unsigned char * data,
|
||||
unsigned int size, unsigned int nTimeStamp)
|
||||
{
|
||||
unsigned char *gBody = nullptr;
|
||||
static int timestamp = 0;
|
||||
timestamp += 20;
|
||||
if (!mFirtACC) {
|
||||
SendAudioSync(2,4,4, timestamp);
|
||||
mFirtACC = 1;
|
||||
}
|
||||
gBody = (unsigned char*)malloc(size + 2);
|
||||
gBody[0] = 0xAF;
|
||||
gBody[1] = 0x01; //aac raw data
|
||||
memcpy(gBody + 2, data + 7, size - 7);
|
||||
int bRet = SendPacket(RTMP_PACKET_TYPE_AUDIO, gBody,
|
||||
size - 7 + 2, timestamp);
|
||||
free(gBody);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void H264RtmpPuser::OnGetCodeFrame(uint8_t * data, int len)
|
||||
{
|
||||
static int timetamp = 0;
|
||||
timetamp += this->mTick;
|
||||
uint8_t *pack = (uint8_t*)malloc(len);
|
||||
memcpy(pack, data, len);
|
||||
mMux.lock();
|
||||
Buffer buf;
|
||||
buf.buf = pack;
|
||||
buf.len = len;
|
||||
buf.type = PAYLOAD_TYPE_VIDEO;
|
||||
this->mPack.push(buf);
|
||||
mMux.unlock();
|
||||
}
|
||||
|
||||
void H264RtmpPuser::ProcessSend()
|
||||
{
|
||||
while (this->mIfStart) {
|
||||
int len = mPack.size();
|
||||
if (!mPack.empty()) {
|
||||
mMux.lock();
|
||||
Buffer buf = mPack.front();
|
||||
mPack.pop();
|
||||
mMux.unlock();
|
||||
//如果是视频帧
|
||||
if (buf.type == PAYLOAD_TYPE_VIDEO) {
|
||||
this->sortAndSendNal(buf.buf, buf.len);
|
||||
|
||||
}// 如果是音频帧
|
||||
if (buf.type == PAYLOAD_TYPE_AUDIO) {
|
||||
this->sendDataPackAAC(buf.buf, buf.len, this->mAudioPts);
|
||||
}
|
||||
free(buf.buf);
|
||||
}
|
||||
msleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
int ThreadEncode(H264RtmpPuser * p)
|
||||
{
|
||||
Debuger::Debug(L"thread started\r\n");
|
||||
if (nullptr == p)
|
||||
return -1;
|
||||
p->ProcessSend();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int H264RtmpPuser::StartPush()
|
||||
{
|
||||
mIfStart = true;
|
||||
this->mThread = new std::thread(ThreadEncode,this);
|
||||
mThreadId = this->mThread->get_id();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int H264RtmpPuser::StopPush()
|
||||
{
|
||||
mIfConnected = false;
|
||||
mIfStart = false;
|
||||
if(mThread != nullptr)
|
||||
this->mThread->join();
|
||||
this->RTMP264_Close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include "librtmp_send264.h"
|
||||
#include "librtmp\rtmp.h"
|
||||
#include "librtmp\rtmp_sys.h"
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "librtmp\amf.h"
|
||||
|
||||
#include "AACAudioCoder.h"
|
||||
#include "sps_decode.h"
|
||||
#include "VideoCoder.h"
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include<iostream>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
#define RTMP_HEAD_SIZE (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE)
|
||||
|
||||
class RtmpPusher
|
||||
{
|
||||
protected:
|
||||
RTMP *m_pRtmp;
|
||||
string mUrl;
|
||||
int mTick = 10;
|
||||
std::mutex mMux;
|
||||
std::thread *mThread;
|
||||
bool mIfConnected = false;
|
||||
std::thread::id mThreadId;
|
||||
public:
|
||||
bool IfConnect();
|
||||
int RTMP264_Connect(const char* url);
|
||||
void RTMP264_Close();
|
||||
int SendPacket(unsigned int nPacketType, unsigned char *data, unsigned int size, unsigned int nTimestamp);
|
||||
int SendVideoPacket(unsigned int nPacketType, unsigned char *data, unsigned int size, unsigned int nTimestamp);
|
||||
int SetTick(int tick) { this->mTick = tick; };
|
||||
virtual int StartPush() { return 0; };
|
||||
RtmpPusher();
|
||||
virtual ~RtmpPusher();
|
||||
};
|
||||
|
||||
/**
|
||||
* _RTMPMetadata
|
||||
* 内部结构体。该结构体主要用于存储和传递元数据信息
|
||||
*/
|
||||
typedef struct _RTMPMetadata
|
||||
{
|
||||
// video, must be h264 type
|
||||
int nWidth;
|
||||
int nHeight;
|
||||
int nFrameRate;
|
||||
unsigned int nSpsLen;
|
||||
unsigned char *Sps;
|
||||
unsigned int nPpsLen;
|
||||
unsigned char *Pps;
|
||||
} RTMPMetadata, *LPRTMPMetadata;
|
||||
|
||||
enum Payload_Type {
|
||||
PAYLOAD_TYPE_VIDEO = 0,
|
||||
PAYLOAD_TYPE_AUDIO = 1
|
||||
};
|
||||
|
||||
typedef struct _T_Buffer {
|
||||
uint8_t *buf;
|
||||
int len;
|
||||
Payload_Type type;
|
||||
}Buffer;
|
||||
|
||||
class H264RtmpPuser : public RtmpPusher ,
|
||||
public VideoCodeObserver,
|
||||
public AAC_CODER::AACAudioCoder::EncodeAudioObserver {
|
||||
private:
|
||||
bool mFirtACC;
|
||||
uint16_t mAudioPts;
|
||||
bool mIfStart = false;
|
||||
// 视频同步包
|
||||
int SendVideoSpsPps(unsigned char *pps, int pps_len,
|
||||
unsigned char * sps, int sps_len, unsigned int nTimeStamp);
|
||||
// 音频同步包
|
||||
int SendAudioSync(int audioType, int sampleIndex, int channel, unsigned int nTimeStamp);
|
||||
int SendAudioData(unsigned char*dat, unsigned int size, unsigned int nTimeStamp);
|
||||
int SendH264Packet(unsigned char *data,
|
||||
unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp);
|
||||
int sendDataPackH264(unsigned char *data,
|
||||
unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp);
|
||||
int sendDataPackAAC(unsigned char *data, unsigned int size, unsigned int nTimeStamp);
|
||||
uint32_t mStartTime;
|
||||
public:
|
||||
|
||||
queue<Buffer> mPack;
|
||||
RTMPMetadata metaData;
|
||||
H264RtmpPuser();
|
||||
int sortAndSendNal(uint8_t *data, int len);
|
||||
|
||||
int SetSpsPps(unsigned char *pps, int pps_len,
|
||||
unsigned char * sps, int sps_len);
|
||||
void OnAudioEncode(const void *frameaddress, uint32_t framelen, uint16_t pts);
|
||||
void OnGetCodeFrame(uint8_t *data, int len);
|
||||
|
||||
void ProcessSend();
|
||||
int StartPush();
|
||||
int StopPush();
|
||||
};
|
||||
|
||||
int ThreadEncode(H264RtmpPuser*p);
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include "H264Docoder.h"
|
||||
#include "CameraCapture.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "sdl/SDL.h"
|
||||
};
|
||||
|
||||
class SDLPlayser : public H264decoder::H264DecodeObserver , public Camera::CameraObserver{
|
||||
public:
|
||||
SDLPlayser(HWND,int ,int, AVPixelFormat);
|
||||
~SDLPlayser();
|
||||
int RenderYuv(void *pBuf,uint32_t size, AVPixelFormat pix);
|
||||
int OnRecieveData(AVFrame *frame);
|
||||
int OnBuffer(double dblSampleTime, BYTE * pBuffer, long lBufferSize) ;
|
||||
int OnCameraData(uint8_t *dat, uint32_t size) ;
|
||||
private:
|
||||
HWND mWindowWnd;
|
||||
//SDL---------------------------
|
||||
int screen_w = 0, screen_h = 0;
|
||||
int mInWidth, mInHeight;
|
||||
SDL_Texture* mTexture;
|
||||
SDL_Rect sdlRect;
|
||||
AVPixelFormat mFormat;
|
||||
SDL_Window *mScreen;
|
||||
SDL_Renderer *mRender;
|
||||
};
|
|
@ -0,0 +1,280 @@
|
|||
#include "VideoCoder.h"
|
||||
#include "Debuger.h"
|
||||
FILE *p = nullptr;
|
||||
int VideoCoder::OnBuffer(double dblSampleTime, BYTE * pBuffer, long lBufferSize)
|
||||
{
|
||||
this->Encode(pBuffer, lBufferSize, AV_PIX_FMT_YUV420P);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoCoder::OnCameraData(uint8_t * dat, uint32_t size)
|
||||
{
|
||||
//std::cout<<"captrue data and into coder"<<std::endl;
|
||||
this->Encode(dat, size, AV_PIX_FMT_YUV420P);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoCoder::SetDestPix(uint8_t width, uint8_t height) {
|
||||
this->mDestHeight = height;
|
||||
this->mDestWidth = width;
|
||||
return 0;
|
||||
}
|
||||
|
||||
VideoCoder::VideoCoder(int width, int height, AVPixelFormat formt):
|
||||
mObserver(nullptr),
|
||||
mFrame(nullptr),
|
||||
mPitureBuffer(nullptr),
|
||||
mFormatCtx(nullptr),
|
||||
mOutputFmt(nullptr),
|
||||
mVideoStream(nullptr),
|
||||
mCodecCtx(nullptr),
|
||||
mCodec(nullptr) {
|
||||
AVCodecID codec_id = AV_CODEC_ID_H264;
|
||||
mCodec = avcodec_find_encoder(codec_id);
|
||||
|
||||
av_register_all();
|
||||
if (nullptr == p) {
|
||||
p = fopen("shit.h264", "wb");
|
||||
}
|
||||
this->mWidth = width;
|
||||
this->mHeight = height;
|
||||
this->mInformat = formt;
|
||||
if (!mCodec) {
|
||||
printf("Codec not found\n");
|
||||
}
|
||||
this->mFormatCtx = avformat_alloc_context();
|
||||
|
||||
//原文链接:https ://blog.csdn.net/leixiaohua1020/article/details/25430425 引用来自雷神的文章,雷神保佑
|
||||
this->mOutputFmt = av_guess_format(NULL, "shit.h264", NULL);
|
||||
this->mFormatCtx->oformat = mOutputFmt;
|
||||
mCodecCtx = avcodec_alloc_context3(mCodec);
|
||||
if (!mCodecCtx) {
|
||||
printf("Could not allocate video codec context\n");
|
||||
}
|
||||
mCodecCtx->bit_rate = 1000;
|
||||
this->mDestHeight = 480;
|
||||
this->mDestWidth = 640;
|
||||
mCodecCtx->width = this->mDestWidth;
|
||||
mCodecCtx->height = this->mDestHeight;
|
||||
mCodecCtx->time_base.num = 1;
|
||||
mCodecCtx->time_base.den = 10;
|
||||
mCodecCtx->max_b_frames = 0;
|
||||
mCodecCtx->qmin = 10;
|
||||
mCodecCtx->qmax = 25;
|
||||
//mCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
mCodecCtx->gop_size = 10;
|
||||
mCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
av_opt_set(mCodecCtx->priv_data, "preset", "superfast", 0);
|
||||
av_opt_set(mCodecCtx->priv_data, "tune", "zerolatency", 0);
|
||||
if (avcodec_open2(mCodecCtx, mCodec, NULL) < 0) {
|
||||
printf("Could not open codec\n");
|
||||
}
|
||||
mFrame = av_frame_alloc();
|
||||
if (!mFrame) {
|
||||
printf("Could not allocate video frame\n");
|
||||
}
|
||||
mFrame->format = mCodecCtx->pix_fmt;
|
||||
mFrame->width = mCodecCtx->width/2;
|
||||
mFrame->height = mCodecCtx->height/2;
|
||||
mFrame->pts = 0;
|
||||
int ret = av_image_alloc(mFrame->data, mFrame->linesize, mCodecCtx->width, mCodecCtx->height,
|
||||
mCodecCtx->pix_fmt, 8);
|
||||
if (ret < 0) {
|
||||
printf("Could not allocate raw picture buffer\n");
|
||||
}
|
||||
|
||||
// 让我们假设分辨率都是不可改变的,AvPack可以复用
|
||||
avformat_write_header(mFormatCtx, NULL);
|
||||
int picture_size = avpicture_get_size(AV_PIX_FMT_YUV420P, mCodecCtx->width, mCodecCtx->height);
|
||||
}
|
||||
|
||||
VideoCoder::~VideoCoder()
|
||||
{
|
||||
fclose(p);
|
||||
}
|
||||
|
||||
void VideoCoder::Encode(uint8_t * src, int size, enum AVPixelFormat format) {
|
||||
uint8_t *pFrame[4];
|
||||
int lineSize[4];
|
||||
static int debugs = 1;
|
||||
//如果不是yuv420p就转成yuv420p
|
||||
int iFramesize;
|
||||
|
||||
av_init_packet(&mAVPack);
|
||||
mAVPack.data = NULL; // packet data will be allocated by the encoder
|
||||
|
||||
int ret = av_image_alloc(pFrame, lineSize, mWidth, mHeight, AV_PIX_FMT_YUV420P, 1);
|
||||
if (ret< 0) {
|
||||
Debuger::Debug(L"Could not allocate destination image\n");
|
||||
}
|
||||
|
||||
if (this->mInformat != AV_PIX_FMT_YUV420P || (this->mDestHeight != mHeight)) {
|
||||
int size = avpicture_get_size(this->mInformat,mWidth,mHeight);
|
||||
this->forceYUV420P(src, size, mInformat, (uint8_t ***)&pFrame,&iFramesize);
|
||||
//仅仅支持yuv420p
|
||||
mFrame->data[0] = pFrame[0]; //Y
|
||||
mFrame->data[1] = pFrame[1]; //U
|
||||
mFrame->data[2] = pFrame[2]; //V
|
||||
}
|
||||
else {
|
||||
mFrame->data[0] = src; //Y
|
||||
mFrame->data[1] = src + mWidth*mHeight; //U
|
||||
mFrame->data[2] = src + mWidth*mHeight + mWidth*mHeight/4; //V
|
||||
}
|
||||
//PTS
|
||||
mFrame->pts++;
|
||||
int got_picture = 0;
|
||||
//Encode
|
||||
avcodec_encode_video2(mCodecCtx, &mAVPack, mFrame, &got_picture);
|
||||
if (got_picture > 0) {
|
||||
if(nullptr != this->mObserver)
|
||||
this->mObserver->OnGetCodeFrame(mAVPack.data, mAVPack.size);
|
||||
}
|
||||
//Debuger::Debug(L"Succeed to encode frame: %5d\tsize:%5d\n", 1, mAVPack.size);
|
||||
fwrite(mAVPack.data, 1, mAVPack.size, p);
|
||||
fflush(p);
|
||||
// 刷新coder,防止包挤压
|
||||
av_packet_unref(&mAVPack);
|
||||
av_freep(&pFrame[0]);
|
||||
free(pFrame[0]);
|
||||
//av_freep(&mFrame->data[0]);
|
||||
//av_freep(&mFrame->data[0]);
|
||||
}
|
||||
|
||||
void VideoCoder::SetOutPutPixel(unsigned int width, unsigned int height)
|
||||
{
|
||||
this->mHeight = height;
|
||||
this->mWidth = width;
|
||||
}
|
||||
|
||||
int VideoCoder::flushCoder(AVFormatContext *fmt_ctx, unsigned int stream_index) {
|
||||
int ret;
|
||||
int got_frame;
|
||||
AVPacket enc_pkt;
|
||||
if (!(this->mFormatCtx->streams[stream_index]->codec->codec->capabilities ))
|
||||
return 0;
|
||||
while (1) {
|
||||
enc_pkt.data = NULL;
|
||||
enc_pkt.size = 0;
|
||||
av_init_packet(&enc_pkt);
|
||||
ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
|
||||
NULL, &got_frame);
|
||||
av_frame_free(NULL);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (!got_frame) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
Debuger::Debug(L"Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
|
||||
/* mux encoded frame */
|
||||
ret = av_write_frame(fmt_ctx, &enc_pkt);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// 强制把其他个数的数据转换成libav可以认得到的数据
|
||||
int VideoCoder::forceYUV420P(uint8_t * src, int size,
|
||||
AVPixelFormat format,uint8_t **dst[4],int *len)
|
||||
{
|
||||
uint8_t *src_data[4];
|
||||
int src_linesize[4];
|
||||
uint8_t *dst_data[4];
|
||||
int dst_linesize[4];
|
||||
struct SwsContext *img_convert_ctx;
|
||||
int ret = 0;
|
||||
|
||||
if (nullptr == dst || nullptr == len) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
int src_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(format));
|
||||
AVPixelFormat dst_pixfmt = AV_PIX_FMT_YUV420P;
|
||||
int dst_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(dst_pixfmt));
|
||||
|
||||
ret = av_image_alloc(src_data, src_linesize, mWidth, mHeight, format, 1);
|
||||
if (ret< 0) {
|
||||
Debuger::Debug(L"Could not allocate source image\n");
|
||||
return -1;
|
||||
}
|
||||
ret = av_image_alloc(dst_data, dst_linesize, mDestWidth, mDestHeight, AV_PIX_FMT_YUV420P, 1);
|
||||
if (ret< 0) {
|
||||
Debuger::Debug(L"Could not allocate destination image\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
img_convert_ctx = sws_alloc_context();
|
||||
//Show AVOption
|
||||
//av_opt_show2(img_convert_ctx, stdout, AV_OPT_FLAG_VIDEO_PARAM, 0);
|
||||
//Set Value
|
||||
av_opt_set_int(img_convert_ctx, "sws_flags", SWS_BICUBIC | SWS_PRINT_INFO, 0);
|
||||
av_opt_set_int(img_convert_ctx, "srcw", mWidth, 0);
|
||||
av_opt_set_int(img_convert_ctx, "srch", mHeight, 0);
|
||||
av_opt_set_int(img_convert_ctx, "src_format", format, 0);
|
||||
av_opt_set_int(img_convert_ctx, "src_range", 1, 0);
|
||||
|
||||
av_opt_set_int(img_convert_ctx, "dstw", mDestWidth, 0);
|
||||
av_opt_set_int(img_convert_ctx, "dsth", mDestHeight, 0);
|
||||
av_opt_set_int(img_convert_ctx, "dst_format", dst_pixfmt, 0);
|
||||
av_opt_set_int(img_convert_ctx, "dst_range", 1, 0);
|
||||
sws_init_context(img_convert_ctx, NULL, NULL);
|
||||
|
||||
// 设置输入
|
||||
switch (format) {
|
||||
case AV_PIX_FMT_GRAY8: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight);
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_YUV420P: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight); //Y
|
||||
memcpy(src_data[1], src + mWidth*mHeight, mWidth*mHeight / 4); //U
|
||||
memcpy(src_data[2], src + mWidth*mHeight * 5 / 4, mWidth*mHeight / 4); //V
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_YUV422P: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight); //Y
|
||||
memcpy(src_data[1], src + mWidth*mHeight, mWidth*mHeight / 2); //U
|
||||
memcpy(src_data[2], src + mWidth*mHeight * 3 / 2, mWidth*mHeight / 2); //V
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_YUV444P: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight); //Y
|
||||
memcpy(src_data[1], src + mWidth*mHeight, mWidth*mHeight); //U
|
||||
memcpy(src_data[2], src + mWidth*mHeight * 2, mWidth*mHeight); //V
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_YUYV422: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight * 2); //Packed
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_RGB24: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight * 3); //Packed
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_RGB32: {
|
||||
memcpy(src_data[0], src, mWidth*mHeight *4); //Packed
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Debuger::Debug(L"Not Support Input Pixel Format.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 转换数据
|
||||
ret = sws_scale(img_convert_ctx, src_data, src_linesize, 0, mHeight, dst_data, dst_linesize);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
memcpy(dst[0], dst_data[0], mDestWidth*mDestHeight);
|
||||
memcpy(dst[1], dst_data[1], mDestWidth*mDestHeight /4);
|
||||
memcpy(dst[2], dst_data[2], mDestWidth*mDestHeight /4);
|
||||
|
||||
*len = mDestWidth*mDestHeight + mDestWidth*mDestHeight / 2;
|
||||
// source此时就不需要了,但是dst要在外面free
|
||||
av_freep(&src_data[0]);
|
||||
av_freep(&dst_data[0]);
|
||||
|
||||
sws_freeContext(img_convert_ctx);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
#ifdef _WIN32
|
||||
#include "Debuger.h"
|
||||
#include "CameraCapture.h"
|
||||
//Windows
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
};
|
||||
#include <iostream>
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class VideoCodeObserver {
|
||||
public:
|
||||
virtual void OnGetCodeFrame(uint8_t *data, int len) {
|
||||
Debuger::Debug(L"get one code %d \r\n", len);
|
||||
}
|
||||
};
|
||||
|
||||
class VideoCoder : public Camera::CameraObserver{
|
||||
private:
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
unsigned int mDestWidth;
|
||||
unsigned int mDestHeight;
|
||||
int mBytePerPixel;
|
||||
enum AVPixelFormat mInformat;
|
||||
AVFormatContext *mFormatCtx;
|
||||
AVOutputFormat *mOutputFmt;
|
||||
AVStream *mVideoStream;
|
||||
AVCodecContext *mCodecCtx;
|
||||
AVCodec *mCodec;
|
||||
AVPacket mAVPack;
|
||||
uint8_t *mPitureBuffer;
|
||||
AVFrame *mFrame;
|
||||
VideoCodeObserver *mObserver;
|
||||
|
||||
public:
|
||||
int OnBuffer(double dblSampleTime, BYTE * pBuffer, long lBufferSize);
|
||||
int OnCameraData(uint8_t *dat, uint32_t size) ;
|
||||
int SetDestPix(uint8_t width,uint8_t height);
|
||||
VideoCoder(int width,int height,AVPixelFormat formt);
|
||||
~VideoCoder();
|
||||
void Encode(uint8_t*src,int size, enum AVPixelFormat format);
|
||||
void SetOberver(VideoCodeObserver *p) {
|
||||
this->mObserver = p;
|
||||
}
|
||||
void SetOutPutPixel(unsigned int width,unsigned int height);
|
||||
private:
|
||||
int flushCoder(AVFormatContext *fmt_ctx, unsigned int stream_index);
|
||||
int forceYUV420P(uint8_t *src, int size, enum AVPixelFormat format, uint8_t ***dst,int *s);
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
#include "audiocaptureff.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
std::string WString2String(const std::wstring& ws)
|
||||
{
|
||||
std::string strLocale = setlocale(LC_ALL, "");
|
||||
const wchar_t* wchSrc = ws.c_str();
|
||||
size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1;
|
||||
char *chDest = new char[nDestSize];
|
||||
memset(chDest, 0, nDestSize);
|
||||
wcstombs(chDest, wchSrc, nDestSize);
|
||||
std::string strResult = chDest;
|
||||
delete[]chDest;
|
||||
setlocale(LC_ALL, strLocale.c_str());
|
||||
return strResult;
|
||||
}
|
||||
#endif
|
||||
|
||||
vector<CaptureAudioFfmpeg::MICInfo> CaptureAudioFfmpeg::EnumSpeakers()
|
||||
{
|
||||
vector<CaptureAudioFfmpeg::MICInfo> ret;
|
||||
std::vector<std::wstring> names;
|
||||
IEnumMoniker *pEnum = nullptr;
|
||||
// Create the System Device Enumerator.
|
||||
ICreateDevEnum *pDevEnum;
|
||||
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
|
||||
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// Create an enumerator for the category.
|
||||
hr = pDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory, &pEnum, 0);
|
||||
if (hr == S_FALSE)
|
||||
{
|
||||
hr = VFW_E_NOT_FOUND; // The category is empty. Treat as an error.
|
||||
}
|
||||
pDevEnum->Release();
|
||||
}
|
||||
|
||||
if (!SUCCEEDED(hr))
|
||||
return ret;
|
||||
|
||||
IMoniker *pMoniker = nullptr;
|
||||
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK)
|
||||
{
|
||||
IPropertyBag *pPropBag;
|
||||
IBindCtx* bindCtx = nullptr;
|
||||
LPOLESTR str = nullptr;
|
||||
VARIANT var;
|
||||
VariantInit(&var);
|
||||
|
||||
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
pMoniker->Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get description or friendly name.
|
||||
hr = pPropBag->Read(L"Description", &var, 0);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
hr = pPropBag->Read(L"FriendlyName", &var, 0);
|
||||
}
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
names.push_back(var.bstrVal);
|
||||
CaptureAudioFfmpeg::MICInfo ele;
|
||||
ele.name = var.bstrVal;
|
||||
ret.push_back(ele);
|
||||
VariantClear(&var);
|
||||
}
|
||||
|
||||
pPropBag->Release();
|
||||
pMoniker->Release();
|
||||
}
|
||||
|
||||
pEnum->Release();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
CaptureAudioFfmpeg::CaptureAudioFfmpeg(uint16_t rate, uint8_t channel)
|
||||
{
|
||||
mSampleRate = rate;
|
||||
mChanel = channel;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static char *dup_wchar_to_utf8(wchar_t *w)
|
||||
{
|
||||
char *s = NULL;
|
||||
int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
|
||||
s = (char *)av_malloc(l);
|
||||
if (s)
|
||||
WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
int CaptureAudioFfmpeg::InitCapture(wstring url, uint16_t rate, uint8_t channel)
|
||||
{
|
||||
string fileAudioInput = dup_wchar_to_utf8((wchar_t *)url.c_str());
|
||||
AVInputFormat* imft = av_find_input_format("dshow");
|
||||
AVDictionary *format_opts = nullptr;
|
||||
av_dict_set_int(&format_opts, "audio_buffer_size", 20, 0);
|
||||
if (0 > avformat_open_input(&mInfmt_ctx, fileAudioInput.c_str(), imft, &format_opts)) {
|
||||
printf("failed input file\n");
|
||||
return -1;
|
||||
}
|
||||
if (0 > avformat_find_stream_info(mInfmt_ctx, NULL)) {
|
||||
printf("failed find stream info\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
return -1;
|
||||
}
|
||||
int audio_index = -1;
|
||||
audio_index = av_find_best_stream(mInfmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
||||
if (-1 == audio_index) {
|
||||
printf("failed find best stream\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
return -1;
|
||||
}
|
||||
//av_dump_format(infmt_ctx, 0, fileAudioInput.c_str(), 1);
|
||||
//END输入文件
|
||||
|
||||
//打开解码器
|
||||
static AVCodec* decodec = avcodec_find_decoder(mInfmt_ctx->streams[0]->codec->codec_id);
|
||||
if (!decodec) {
|
||||
printf("failed find decoder\n");
|
||||
return -1;
|
||||
}
|
||||
if (0 > avcodec_open2(mInfmt_ctx->streams[0]->codec, decodec, NULL)) {
|
||||
printf("failed open decoder\n");
|
||||
return -1;
|
||||
}
|
||||
//END解码器
|
||||
//重采样初始化
|
||||
initAudioFilters();
|
||||
//END重采样初始化
|
||||
//编码器
|
||||
static AVCodec* codec = NULL;
|
||||
//codec = avcodec_find_encoder_by_name("libmp3lame");
|
||||
codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
|
||||
static AVCodecContext* codec_ctx = NULL;
|
||||
codec_ctx = avcodec_alloc_context3(codec);
|
||||
// codec_ctx->bit_rate = 64000;
|
||||
// inputContext->streams[0]->codec
|
||||
codec_ctx->codec = codec;
|
||||
codec_ctx->sample_rate = 48000;
|
||||
codec_ctx->channel_layout = 3;
|
||||
codec_ctx->channels = 2;
|
||||
//codec_ctx->frame_size = 1024;
|
||||
codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
||||
codec_ctx->codec_tag = 0;
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
if (0 > avcodec_open2(codec_ctx, codec, NULL)) {
|
||||
printf("failed open coder\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
avcodec_free_context(&codec_ctx);
|
||||
return -1;
|
||||
}
|
||||
//END编码器
|
||||
//输出文件
|
||||
AVFormatContext* outfmt_ctx = NULL;
|
||||
if (0 > avformat_alloc_output_context2(&outfmt_ctx, NULL, NULL, "aac.aac")) {
|
||||
printf("failed alloc outputcontext\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
avcodec_free_context(&codec_ctx);
|
||||
return -1;
|
||||
}
|
||||
AVStream* out_stream = avformat_new_stream(outfmt_ctx, codec_ctx->codec);
|
||||
if (!out_stream) {
|
||||
printf("failed new stream\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
avcodec_free_context(&codec_ctx);
|
||||
avformat_close_input(&outfmt_ctx);
|
||||
return -1;
|
||||
}
|
||||
avcodec_copy_context(out_stream->codec, codec_ctx);
|
||||
// if (0 > avio_open(&outfmt_ctx->pb, "rtmp://localhost/testlive", AVIO_FLAG_WRITE)) {
|
||||
if (0 > avio_open(&outfmt_ctx->pb, "aac.aac", AVIO_FLAG_WRITE)) {
|
||||
printf("failed to open outfile\n");
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
avcodec_free_context(&codec_ctx);
|
||||
avformat_close_input(&outfmt_ctx);
|
||||
return -1;
|
||||
}
|
||||
avformat_write_header(outfmt_ctx, NULL);
|
||||
//END输出文件
|
||||
#if 0
|
||||
AVFrame* Frame = av_frame_alloc();
|
||||
Frame->nb_samples = codec_ctx->frame_size;
|
||||
Frame->format = codec_ctx->sample_fmt;
|
||||
Frame->channel_layout = codec_ctx->channel_layout;
|
||||
int size = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size,
|
||||
codec_ctx->sample_fmt, 1);
|
||||
uint8_t* frame_buf = (uint8_t *)av_malloc(size);
|
||||
avcodec_fill_audio_frame(Frame, codec_ctx->channels, codec_ctx->sample_fmt, (const uint8_t*)frame_buf, size, 1);
|
||||
int64_t in_channel_layout = av_get_default_channel_layout(codec_ctx->channels);
|
||||
AVPacket pkt;
|
||||
av_new_packet(&pkt, size);
|
||||
pkt.data = NULL;
|
||||
int got_frame = -1;
|
||||
int delayedFrame = 0;
|
||||
static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
|
||||
int audioCount = 0;
|
||||
const uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
|
||||
AVFrame* Frame1 = av_frame_alloc();
|
||||
#endif
|
||||
int loop = 1;
|
||||
int delayedFrame = 0;
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
packet.data = NULL;
|
||||
packet.size = 0;
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = NULL;
|
||||
pkt.size = 0;
|
||||
|
||||
AVFrame* pSrcAudioFrame = av_frame_alloc();
|
||||
int got_frame = 0;
|
||||
|
||||
while (1) {
|
||||
av_read_frame(mInfmt_ctx, &packet);
|
||||
loop++;
|
||||
if (packet.stream_index == audio_index) {
|
||||
auto filterFrame = DecodeAudio(&packet, pSrcAudioFrame);
|
||||
if (filterFrame) {
|
||||
avcodec_encode_audio2(codec_ctx, &pkt, filterFrame, &got_frame);
|
||||
if (got_frame) {
|
||||
#if 1
|
||||
auto streamTimeBase = outfmt_ctx->streams[pkt.stream_index]->time_base.den;
|
||||
auto codecTimeBase = outfmt_ctx->streams[pkt.stream_index]->codec->time_base.den;
|
||||
pkt.pts = pkt.dts = (1024 * streamTimeBase * mAudioCount) / codecTimeBase;
|
||||
mAudioCount++;
|
||||
auto inputStream = mInfmt_ctx->streams[pkt.stream_index];
|
||||
auto outputStream = outfmt_ctx->streams[pkt.stream_index];
|
||||
av_packet_rescale_ts(&pkt, inputStream->time_base, outputStream->time_base);
|
||||
#endif
|
||||
// pkt.stream_index = out_stream->index;
|
||||
av_interleaved_write_frame(outfmt_ctx, &pkt);
|
||||
av_packet_unref(&pkt);
|
||||
printf("output frame %3d\n", loop - delayedFrame);
|
||||
}
|
||||
else {
|
||||
delayedFrame++;
|
||||
av_packet_unref(&pkt);
|
||||
printf("no output frame\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
flush_encoder(outfmt_ctx, 0);
|
||||
av_write_trailer(outfmt_ctx);
|
||||
//av_free(Frame);
|
||||
av_free(pSrcAudioFrame);
|
||||
avio_close(outfmt_ctx->pb);
|
||||
avformat_close_input(&mInfmt_ctx);
|
||||
//avformat_close_input(&outfmt_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CaptureAudioFfmpeg::initAudioFilters()
|
||||
{
|
||||
char args[512];
|
||||
int ret;
|
||||
AVFilter *abuffersrc = (AVFilter *)avfilter_get_by_name("abuffer");
|
||||
AVFilter *abuffersink = (AVFilter *)avfilter_get_by_name("abuffersink");
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
|
||||
auto audioDecoderContext = mInfmt_ctx->streams[0]->codec;
|
||||
if (!audioDecoderContext->channel_layout)
|
||||
audioDecoderContext->channel_layout = av_get_default_channel_layout(audioDecoderContext->channels);
|
||||
|
||||
static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE };
|
||||
static const uint64_t out_channel_layouts[] = { audioDecoderContext->channel_layout};
|
||||
static const int out_sample_rates[] = { audioDecoderContext->sample_rate , -1 };
|
||||
|
||||
AVRational time_base = mInfmt_ctx->streams[0]->time_base;
|
||||
mFilterGraph = avfilter_graph_alloc();
|
||||
mFilterGraph->nb_threads = 1;
|
||||
|
||||
sprintf_s(args, sizeof(args),
|
||||
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x",
|
||||
time_base.num, time_base.den, audioDecoderContext->sample_rate,
|
||||
av_get_sample_fmt_name(audioDecoderContext->sample_fmt),
|
||||
audioDecoderContext->channel_layout);
|
||||
|
||||
ret = avfilter_graph_create_filter(&mBuffersrcCtx, abuffersrc, "in",
|
||||
args, NULL, mFilterGraph);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* buffer audio sink: to terminate the filter chain. */
|
||||
ret = avfilter_graph_create_filter(&mBuffersinkCtx, abuffersink, "out",
|
||||
NULL, NULL, mFilterGraph);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = av_opt_set_int_list(mBuffersinkCtx, "sample_fmts", out_sample_fmts, -1,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = av_opt_set_int_list(mBuffersinkCtx, "channel_layouts", out_channel_layouts, -1,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = av_opt_set_int_list(mBuffersinkCtx, "sample_rates", out_sample_rates, -1,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Endpoints for the filter graph. */
|
||||
outputs->name = av_strdup("in");
|
||||
outputs->filter_ctx = mBuffersrcCtx;;
|
||||
outputs->pad_idx = 0;
|
||||
outputs->next = NULL;
|
||||
|
||||
inputs->name = av_strdup("out");
|
||||
inputs->filter_ctx = mBuffersinkCtx;
|
||||
inputs->pad_idx = 0;
|
||||
inputs->next = NULL;
|
||||
|
||||
if ((ret = avfilter_graph_parse_ptr(mFilterGraph, "anull",
|
||||
&inputs, &outputs, nullptr)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = avfilter_graph_config(mFilterGraph, NULL)) < 0)
|
||||
return ret;
|
||||
|
||||
av_buffersink_set_frame_size(mBuffersinkCtx, 1024);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CaptureAudioFfmpeg::flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index)
|
||||
{
|
||||
int ret;
|
||||
int got_frame;
|
||||
AVPacket enc_pkt;
|
||||
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
|
||||
0x0020))
|
||||
return 0;
|
||||
while (1) {
|
||||
enc_pkt.data = NULL;
|
||||
enc_pkt.size = 0;
|
||||
av_init_packet(&enc_pkt);
|
||||
ret = avcodec_encode_audio2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
|
||||
NULL, &got_frame);
|
||||
av_frame_free(NULL);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (!got_frame) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
|
||||
/* mux encoded frame */
|
||||
ret = av_write_frame(fmt_ctx, &enc_pkt);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
|
||||
|
||||
}
|
||||
|
||||
AVFrame *CaptureAudioFfmpeg::DecodeAudio(AVPacket *packet, AVFrame *pSrcAudioFrame)
|
||||
{
|
||||
AVStream * stream = mInfmt_ctx->streams[0];
|
||||
AVCodecContext* codecContext = stream->codec;
|
||||
int gotFrame;
|
||||
AVFrame *filtFrame = nullptr;
|
||||
auto length = avcodec_decode_audio4(codecContext, pSrcAudioFrame, &gotFrame, packet);
|
||||
if (length >= 0 && gotFrame != 0)
|
||||
{
|
||||
if (av_buffersrc_add_frame_flags(mBuffersrcCtx, pSrcAudioFrame, AV_BUFFERSRC_FLAG_PUSH) < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "buffe src add frame error!\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
filtFrame = av_frame_alloc();
|
||||
int ret = av_buffersink_get_frame_flags(mBuffersinkCtx, filtFrame, AV_BUFFERSINK_FLAG_NO_REQUEST);
|
||||
if (ret < 0)
|
||||
{
|
||||
av_frame_free(&filtFrame);
|
||||
goto error;
|
||||
}
|
||||
return filtFrame;
|
||||
}
|
||||
error:
|
||||
return nullptr;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef AUDIOCAPTUREFF_H
|
||||
#define AUDIOCAPTUREFF_H
|
||||
|
||||
#include "stdint.h"
|
||||
#include "../third/portaudio/portaudio.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
//Windows
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavfilter/buffersrc.h"
|
||||
#include "libavfilter/buffersink.h"
|
||||
};
|
||||
#include <functional>
|
||||
#include <dshow.h>
|
||||
#include <windows.h>
|
||||
#include "qedit.h"
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include "guiddef.h"
|
||||
using namespace std;
|
||||
|
||||
|
||||
class CaptureAudioFfmpeg {
|
||||
public:
|
||||
class CaptureAudioObserver {
|
||||
public:
|
||||
virtual void OnAudioData(const void *frameaddress, uint32_t framelen) {};
|
||||
};
|
||||
typedef struct _T_MicInfo
|
||||
{
|
||||
wstring name;
|
||||
int index;
|
||||
}MICInfo;
|
||||
enum CAP_STATUS {
|
||||
RUNNING = 1,
|
||||
STOP = 2,
|
||||
PAUSE = 3,
|
||||
FAIL = 4,
|
||||
};
|
||||
|
||||
vector<CaptureAudioFfmpeg::MICInfo> EnumSpeakers();
|
||||
CaptureAudioFfmpeg(uint16_t rate, uint8_t channel);
|
||||
int InitCapture(wstring url,uint16_t rate,uint8_t channel);
|
||||
|
||||
/*
|
||||
~CaptureAudio();
|
||||
int StartCapture();
|
||||
void StopCapture();
|
||||
int SetObserver(CaptureAudioObserver*);
|
||||
int OnCallBack(const void* input, void* output, unsigned long frameCount);
|
||||
void AddCnt(unsigned int x) {this->mSize += x;};
|
||||
*/
|
||||
private:
|
||||
std::thread mThread;
|
||||
uint16_t mSampleRate; //采样率
|
||||
uint16_t mChanel; //通道号
|
||||
uint16_t mSamplefmt;
|
||||
unsigned long mSize;
|
||||
CAP_STATUS mStatus;
|
||||
CaptureAudioObserver *observer;
|
||||
int initAudioFilters();
|
||||
AVFormatContext *mInfmt_ctx = nullptr;
|
||||
AVFormatContext * mOutfmt_ctx = nullptr;
|
||||
int64_t mLastReadPacktTime;
|
||||
AVFilterContext *mBuffersinkCtx = nullptr;
|
||||
AVFilterContext *mBuffersrcCtx = nullptr;
|
||||
AVFilterGraph *mFilterGraph = nullptr;
|
||||
AVCodecContext* mOutPutAudioEncContext = nullptr;
|
||||
int64_t mAudioCount = 0;
|
||||
|
||||
int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index);
|
||||
AVFrame *DecodeAudio(AVPacket* packet, AVFrame*pSrcAudioFrame);
|
||||
};
|
||||
|
||||
#endif // AUDIOCAPTUREFF_H
|
|
@ -0,0 +1,177 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define MAX_LEN (1*1024*1024)
|
||||
#define POSITIVE_HEIGHT (1)
|
||||
|
||||
/*12Bytes*/
|
||||
typedef struct /**** BMP file header structure ****/
|
||||
{
|
||||
unsigned int bfSize; /* Size of file */
|
||||
unsigned short bfReserved1; /* Reserved */
|
||||
unsigned short bfReserved2; /* ... */
|
||||
unsigned int bfOffBits; /* Offset to bitmap data */
|
||||
}BITMAPFILEHEADER;
|
||||
|
||||
/*40Bytes*/
|
||||
typedef struct /**** BMP file info structure ****/
|
||||
{
|
||||
unsigned int biSize; /* Size of info header */
|
||||
int biWidth; /* Width of image */
|
||||
int biHeight; /* Height of image */
|
||||
unsigned short biPlanes; /* Number of color planes */
|
||||
unsigned short biBitCount; /* Number of bits per pixel */
|
||||
unsigned int biCompression; /* Type of compression to use */
|
||||
unsigned int biSizeImage; /* Size of image data */
|
||||
int biXPelsPerMeter; /* X pixels per meter */
|
||||
int biYPelsPerMeter; /* Y pixels per meter */
|
||||
unsigned int biClrUsed; /* Number of colors used */
|
||||
unsigned int biClrImportant; /* Number of important colors */
|
||||
}BITMAPINFOHEADER;
|
||||
|
||||
int simplest_rgb24_to_bmp(const char* rgb24Path, int w, int h, const char* bmpPath)
|
||||
{
|
||||
int s32Ret = 0;
|
||||
int fd_ori = -1;
|
||||
int fd_bmp = -1;
|
||||
int headerSize = 0;
|
||||
int i = 0;//for circle
|
||||
int j = 0;//for circle
|
||||
unsigned char temp = 0;
|
||||
|
||||
unsigned char readBuff[MAX_LEN] = {'\0'};
|
||||
memset(readBuff, 0, sizeof(readBuff));
|
||||
|
||||
#ifdef POSITIVE_HEIGHT
|
||||
unsigned char readBuff4Ph[MAX_LEN] = {'\0'};
|
||||
memset(readBuff4Ph, 0, sizeof(readBuff4Ph));
|
||||
#endif
|
||||
|
||||
char bfType[2] = {'B', 'M'};
|
||||
|
||||
BITMAPFILEHEADER myHead;
|
||||
BITMAPINFOHEADER myHeadInfo;
|
||||
memset(&myHead, 0, sizeof(myHead));
|
||||
memset(&myHeadInfo, 0, sizeof(myHeadInfo));
|
||||
printf("sizeof(myHead) = %d\n", sizeof(myHead));
|
||||
printf("sizeof(myHeadInfo) = %d\n", sizeof(myHeadInfo));
|
||||
|
||||
/*myHead*/
|
||||
headerSize = sizeof(bfType) + sizeof(myHead) + sizeof(myHeadInfo);
|
||||
myHead.bfSize = headerSize + w*h*3;
|
||||
myHead.bfOffBits = headerSize;
|
||||
|
||||
/*myHeadInfo*/
|
||||
myHeadInfo.biSize = sizeof(myHeadInfo);
|
||||
myHeadInfo.biWidth = w;
|
||||
|
||||
#ifndef POSITIVE_HEIGHT
|
||||
myHeadInfo.biHeight = -1 * h;
|
||||
#else
|
||||
myHeadInfo.biHeight = h;
|
||||
#endif
|
||||
|
||||
myHeadInfo.biPlanes = 1;
|
||||
myHeadInfo.biBitCount = 24;
|
||||
myHeadInfo.biSizeImage = w*h*3;
|
||||
|
||||
/*open files*/
|
||||
fd_ori = open(rgb24Path, O_RDONLY);
|
||||
if(fd_ori < 0)
|
||||
{
|
||||
printf("open rgb24 failed!\n");
|
||||
return -1;
|
||||
}
|
||||
printf("open rgb24 success!\n");
|
||||
|
||||
fd_bmp = open(bmpPath, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 777);
|
||||
if(fd_bmp < 0)
|
||||
{
|
||||
printf("open bmp failed!\n");
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
printf("open bmp success!\n");
|
||||
|
||||
/*read*/
|
||||
memset(readBuff, 0, sizeof(readBuff));
|
||||
s32Ret = read(fd_ori, readBuff, sizeof(readBuff));
|
||||
if((s32Ret < 0) || (s32Ret != w*h*3))
|
||||
{
|
||||
printf("read RGB file failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
printf("read RGB file success!\n");
|
||||
|
||||
/*change R-G-B to B-G-R*/
|
||||
for(i = 0; i < (w*h); i++)
|
||||
{
|
||||
temp = *(readBuff + i*3);
|
||||
*(readBuff + i*3) = *(readBuff + i*3 + 2);
|
||||
*(readBuff + i*3 + 2) = temp;
|
||||
}
|
||||
|
||||
/*positive height storage sequence:left-right down-up*/
|
||||
#ifdef POSITIVE_HEIGHT
|
||||
for(i = (h - 1), j = 0; i >= 0; i--, j++)
|
||||
{
|
||||
memcpy(readBuff4Ph + j*w*3, readBuff + i*w*3, w*3);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*write-4 parts*/
|
||||
s32Ret = write(fd_bmp, bfType, sizeof(bfType));
|
||||
if(s32Ret < 0)
|
||||
{
|
||||
printf("write bfType failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
s32Ret = write(fd_bmp, &myHead, sizeof(myHead));
|
||||
if(s32Ret < 0)
|
||||
{
|
||||
printf("write myHead failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
s32Ret = write(fd_bmp, &myHeadInfo, sizeof(myHeadInfo));
|
||||
if(s32Ret < 0)
|
||||
{
|
||||
printf("write myHeadInfo failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
#ifdef POSITIVE_HEIGHT
|
||||
s32Ret = write(fd_bmp, readBuff4Ph, w*h*3);
|
||||
if(s32Ret < 0)
|
||||
{
|
||||
printf("write readBuff4Ph failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
printf("write readBuff4Ph success!\n");
|
||||
#else
|
||||
s32Ret = write(fd_bmp, readBuff, w*h*3);
|
||||
if(s32Ret < 0)
|
||||
{
|
||||
printf("write readBuff failed!\n");
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return -1;
|
||||
}
|
||||
printf("write readBuff success!\n");
|
||||
#endif
|
||||
|
||||
close(fd_bmp);
|
||||
close(fd_ori);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Simplest Librtmp Send 264
|
||||
*
|
||||
* 雷霄骅,张晖
|
||||
* leixiaohua1020@126.com
|
||||
* zhanghuicuc@gmail.com
|
||||
* 中国传媒大学/数字电视技术
|
||||
* Communication University of China / Digital TV Technology
|
||||
* http://blog.csdn.net/leixiaohua1020
|
||||
*
|
||||
* 本程序用于将内存中的H.264数据推送至RTMP流媒体服务器。
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 初始化并连接到服务器
|
||||
*
|
||||
* @param url 服务器上对应webapp的地址
|
||||
*
|
||||
* @成功则返回1 , 失败则返回0
|
||||
*/
|
||||
int RTMP264_Connect(const char* url);
|
||||
|
||||
/**
|
||||
* 将内存中的一段H.264编码的视频数据利用RTMP协议发送到服务器
|
||||
*
|
||||
* @param read_buffer 回调函数,当数据不足的时候,系统会自动调用该函数获取输入数据。
|
||||
* 2个参数功能:
|
||||
* uint8_t *buf:外部数据送至该地址
|
||||
* int buf_size:外部数据大小
|
||||
* 返回值:成功读取的内存大小
|
||||
* @成功则返回1 , 失败则返回0
|
||||
*/
|
||||
int RTMP264_Send(int (*read_buffer)(unsigned char *buf, int buf_size));
|
||||
|
||||
/**
|
||||
* 断开连接,释放相关的资源。
|
||||
*
|
||||
*/
|
||||
void RTMP264_Close();
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
#include "screen_capture.h"
|
||||
|
||||
#include <conio.h>
|
||||
#include <stdio.h>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
|
||||
#if _MSC_VER >= 1600
|
||||
#pragma execution_character_set("utf-8")
|
||||
#endif
|
||||
|
||||
|
||||
#define WIDEN2(x) L ## x
|
||||
#define WIDEN(x) WIDEN2(x)
|
||||
#define __WFILE__ WIDEN(__FILE__)
|
||||
#define HRCHECK(__expr) {hr=(__expr);if(FAILED(hr)){wprintf(L"FAILURE 0x%08X (%i)\n\tline: %u file: '%s'\n\texpr: '" WIDEN(#__expr) L"'\n",hr, hr, __LINE__,__WFILE__);goto cleanup;}}
|
||||
#define RELEASE(__p) {if(__p!=nullptr){__p->Release();__p=nullptr;}}
|
||||
|
||||
// ȱenum·½·¨
|
||||
// https://www.gamedev.net/forums/topic/132636-enum-displaymode--dx9--false/
|
||||
|
||||
HRESULT SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride,
|
||||
LPBYTE pixels, LPWSTR filePath, const GUID &format)
|
||||
{
|
||||
if (!filePath || !pixels)
|
||||
return E_INVALIDARG;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
IWICImagingFactory *factory = nullptr;
|
||||
IWICBitmapEncoder *encoder = nullptr;
|
||||
IWICBitmapFrameEncode *frame = nullptr;
|
||||
IWICStream *stream = nullptr;
|
||||
GUID pf = GUID_WICPixelFormat32bppPBGRA;
|
||||
BOOL coInit = CoInitialize(nullptr);
|
||||
|
||||
HRCHECK(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)));
|
||||
HRCHECK(factory->CreateStream(&stream));
|
||||
HRCHECK(stream->InitializeFromFilename(filePath, GENERIC_WRITE));
|
||||
HRCHECK(factory->CreateEncoder(format, nullptr, &encoder));
|
||||
HRCHECK(encoder->Initialize(stream, WICBitmapEncoderNoCache));
|
||||
HRCHECK(encoder->CreateNewFrame(&frame, nullptr)); // we don't use options here
|
||||
HRCHECK(frame->Initialize(nullptr)); // we dont' use any options here
|
||||
HRCHECK(frame->SetSize(width, height));
|
||||
HRCHECK(frame->SetPixelFormat(&pf));
|
||||
HRCHECK(frame->WritePixels(height, stride, stride * height, pixels));
|
||||
HRCHECK(frame->Commit());
|
||||
HRCHECK(encoder->Commit());
|
||||
|
||||
cleanup:
|
||||
RELEASE(stream);
|
||||
RELEASE(frame);
|
||||
RELEASE(encoder);
|
||||
RELEASE(factory);
|
||||
if (coInit) CoUninitialize();
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT Direct3D9TakeScreenshots(UINT adapter, UINT count)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
IDirect3D9 *d3d = nullptr;
|
||||
IDirect3DDevice9 *device = nullptr;
|
||||
IDirect3DSurface9 *surface = nullptr;
|
||||
D3DPRESENT_PARAMETERS parameters = { 0 };
|
||||
D3DDISPLAYMODE mode;
|
||||
D3DLOCKED_RECT rc;
|
||||
UINT pitch;
|
||||
SYSTEMTIME st;
|
||||
LPBYTE *shots = nullptr;
|
||||
|
||||
// init D3D and get screen size
|
||||
d3d = Direct3DCreate9(D3D_SDK_VERSION);
|
||||
HRCHECK(d3d->GetAdapterDisplayMode(adapter, &mode));
|
||||
|
||||
parameters.Windowed = TRUE;
|
||||
parameters.BackBufferCount = 1;
|
||||
parameters.BackBufferHeight = mode.Height;
|
||||
parameters.BackBufferWidth = mode.Width;
|
||||
parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
parameters.hDeviceWindow = NULL;
|
||||
|
||||
// create device & capture surface
|
||||
HRCHECK(d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, NULL, D3DCREATE_SOFTWARE_VERTEXPROCESSING, ¶meters, &device));
|
||||
HRCHECK(device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr));
|
||||
|
||||
// compute the required buffer size
|
||||
HRCHECK(surface->LockRect(&rc, NULL, 0));
|
||||
pitch = rc.Pitch;
|
||||
HRCHECK(surface->UnlockRect());
|
||||
|
||||
// allocate screenshots buffers
|
||||
shots = new LPBYTE[count];
|
||||
for (UINT i = 0; i < count; i++)
|
||||
{
|
||||
shots[i] = new BYTE[pitch * mode.Height];
|
||||
}
|
||||
|
||||
GetSystemTime(&st); // measure the time we spend doing <count> captures
|
||||
wprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
||||
for (UINT i = 0; i < count; i++)
|
||||
{
|
||||
// get the data
|
||||
HRCHECK(device->GetFrontBufferData(0, surface));
|
||||
|
||||
// copy it into our buffers
|
||||
HRCHECK(surface->LockRect(&rc, NULL, 0));
|
||||
CopyMemory(shots[i], rc.pBits, rc.Pitch * mode.Height);
|
||||
HRCHECK(surface->UnlockRect());
|
||||
}
|
||||
GetSystemTime(&st);
|
||||
wprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
||||
|
||||
// save all screenshots
|
||||
for (UINT i = 0; i < count; i++)
|
||||
{
|
||||
WCHAR file[100];
|
||||
wsprintf(file, L"cap%i.png", i);
|
||||
HRCHECK(SavePixelsToFile32bppPBGRA(mode.Width, mode.Height, pitch, shots[i], file, GUID_ContainerFormatPng));
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (shots != nullptr)
|
||||
{
|
||||
for (UINT i = 0; i < count; i++)
|
||||
{
|
||||
delete shots[i];
|
||||
}
|
||||
delete[] shots;
|
||||
}
|
||||
RELEASE(surface);
|
||||
RELEASE(device);
|
||||
RELEASE(d3d);
|
||||
return hr;
|
||||
}
|
||||
|
||||
ScreenCapture::ScreenCapture()
|
||||
{
|
||||
m_d3d9_dev = ::Direct3DCreate9(D3D_SDK_VERSION);
|
||||
|
||||
}
|
||||
|
||||
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,HDC hdcMonitor,
|
||||
LPRECT lprcMonitor,LPARAM dwData)
|
||||
{
|
||||
MONITORINFOEX mi;
|
||||
mi.cbSize=sizeof(MONITORINFOEX);
|
||||
GetMonitorInfo(hMonitor,&mi);
|
||||
qDebug()<<QString::asprintf("Device name:%s\t",mi.szDevice);
|
||||
if(mi.dwFlags==MONITORINFOF_PRIMARY) printf("Primary monitor!\n");
|
||||
else printf("\n");
|
||||
qDebug()<<QString::asprintf("Monitor rectangle:(%d,%d,%d,%d)\n",mi.rcMonitor.left,mi.rcMonitor.top,
|
||||
mi.rcMonitor.right,mi.rcMonitor.bottom);
|
||||
qDebug()<<QString::asprintf("Work rectangle:(%d,%d,%d,%d)\n",mi.rcWork.left,mi.rcWork.top,
|
||||
mi.rcWork.right,mi.rcWork.bottom);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenCapture::EnumScreen()
|
||||
{
|
||||
// EnumDisplayMonitors(NULL,NULL,MonitorEnumProc,NULL);
|
||||
if(m_d3d9_dev == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
D3DADAPTER_IDENTIFIER9 adapterID; // Used to store device info
|
||||
char strBuffer[20480];
|
||||
DWORD dwDisplayCount = m_d3d9_dev->GetAdapterCount();
|
||||
for(DWORD i = 0; i < dwDisplayCount; i++)
|
||||
{
|
||||
if( m_d3d9_dev->GetAdapterIdentifier( i/*D3DADAPTER_DEFAULT*/, 0,&adapterID ) != D3D_OK )
|
||||
{
|
||||
return;
|
||||
}
|
||||
qDebug()<<adapterID.DeviceName;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef SCREEN_CAPTURE
|
||||
#define SCREEN_CAPTURE
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Wincodec.h> // we use WIC for saving images
|
||||
#include <d3d9.h> // DirectX 9 header
|
||||
#include <d3d9helper.h>
|
||||
|
||||
|
||||
HRESULT Direct3D9TakeScreenshots(UINT adapter, UINT count);
|
||||
|
||||
|
||||
class ScreenCapture
|
||||
{
|
||||
public:
|
||||
ScreenCapture();
|
||||
void EnumScreen();
|
||||
void PrintDisplayModeInfo(IDirect3D9 *pD3D, D3DFORMAT fmt);
|
||||
private:
|
||||
IDirect3D9* m_d3d9_dev = nullptr;
|
||||
};
|
||||
|
||||
#endif // SCREEN_CAPTURE_H
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* Simplest Librtmp Send 264
|
||||
*
|
||||
* 雷霄骅,张晖
|
||||
* leixiaohua1020@126.com
|
||||
* zhanghuicuc@gmail.com
|
||||
* 中国传媒大学/数字电视技术
|
||||
* Communication University of China / Digital TV Technology
|
||||
* http://blog.csdn.net/leixiaohua1020
|
||||
*
|
||||
* 本程序用于将内存中的H.264数据推送至RTMP流媒体服务器。
|
||||
*
|
||||
*/
|
||||
#include "sps_decode.h"
|
||||
|
||||
|
||||
typedef unsigned int UINT;
|
||||
typedef unsigned char BYTE;
|
||||
typedef unsigned long DWORD;
|
||||
|
||||
UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit)
|
||||
{
|
||||
//计算0bit的个数
|
||||
UINT nZeroNum = 0;
|
||||
while (nStartBit < nLen * 8)
|
||||
{
|
||||
if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) //&:按位与,%取余
|
||||
{
|
||||
break;
|
||||
}
|
||||
nZeroNum++;
|
||||
nStartBit++;
|
||||
}
|
||||
nStartBit ++;
|
||||
|
||||
|
||||
//计算结果
|
||||
DWORD dwRet = 0;
|
||||
for (UINT i=0; i<nZeroNum; i++)
|
||||
{
|
||||
dwRet <<= 1;
|
||||
if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
|
||||
{
|
||||
dwRet += 1;
|
||||
}
|
||||
nStartBit++;
|
||||
}
|
||||
return (1 << nZeroNum) - 1 + dwRet;
|
||||
}
|
||||
|
||||
|
||||
int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit)
|
||||
{
|
||||
int UeVal=Ue(pBuff,nLen,nStartBit);
|
||||
double k=UeVal;
|
||||
int nValue=ceil(k/2);
|
||||
//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
|
||||
if (UeVal % 2==0)
|
||||
nValue=-nValue;
|
||||
return nValue;
|
||||
}
|
||||
|
||||
|
||||
DWORD u(UINT BitCount,BYTE * buf,UINT &nStartBit)
|
||||
{
|
||||
DWORD dwRet = 0;
|
||||
for (UINT i=0; i<BitCount; i++)
|
||||
{
|
||||
dwRet <<= 1;
|
||||
if (buf[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
|
||||
{
|
||||
dwRet += 1;
|
||||
}
|
||||
nStartBit++;
|
||||
}
|
||||
return dwRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* H264的NAL起始码防竞争机制
|
||||
*
|
||||
* @param buf SPS数据内容
|
||||
*
|
||||
* @无返回值
|
||||
*/
|
||||
void de_emulation_prevention(BYTE* buf,unsigned int* buf_size)
|
||||
{
|
||||
int i=0,j=0;
|
||||
BYTE* tmp_ptr = nullptr;
|
||||
unsigned int tmp_buf_size=0;
|
||||
int val=0;
|
||||
|
||||
tmp_ptr=buf;
|
||||
tmp_buf_size=*buf_size;
|
||||
for(i=0;i<(tmp_buf_size-2);i++)
|
||||
{
|
||||
//check for 0x000003
|
||||
val=(tmp_ptr[i]^0x00) +(tmp_ptr[i+1]^0x00)+(tmp_ptr[i+2]^0x03);
|
||||
if(val==0)
|
||||
{
|
||||
//kick out 0x03
|
||||
for(j=i+2;j<tmp_buf_size-1;j++)
|
||||
tmp_ptr[j]=tmp_ptr[j+1];
|
||||
|
||||
//and so we should devrease bufsize
|
||||
(*buf_size)--;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码SPS,获取视频图像宽、高信息
|
||||
*
|
||||
* @param buf SPS数据内容
|
||||
* @param nLen SPS数据的长度
|
||||
* @param width 图像宽度
|
||||
* @param height 图像高度
|
||||
|
||||
* @成功则返回1 , 失败则返回0
|
||||
*/
|
||||
int h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height,int &fps)
|
||||
{
|
||||
UINT StartBit=0;
|
||||
fps=0;
|
||||
de_emulation_prevention(buf,&nLen);
|
||||
|
||||
int forbidden_zero_bit = u(1,buf,StartBit);
|
||||
int nal_ref_idc = u(2,buf,StartBit);
|
||||
int nal_unit_type = u(5,buf,StartBit);
|
||||
if(nal_unit_type==7)
|
||||
{
|
||||
int profile_idc=u(8,buf,StartBit);
|
||||
int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
|
||||
int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
|
||||
int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
|
||||
int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
|
||||
int reserved_zero_4bits=u(4,buf,StartBit);
|
||||
int level_idc=u(8,buf,StartBit);
|
||||
|
||||
int seq_parameter_set_id=Ue(buf,nLen,StartBit);
|
||||
|
||||
if( profile_idc == 100 || profile_idc == 110 ||
|
||||
profile_idc == 122 || profile_idc == 144 )
|
||||
{
|
||||
int chroma_format_idc=Ue(buf,nLen,StartBit);
|
||||
if( chroma_format_idc == 3 )
|
||||
int residual_colour_transform_flag=u(1,buf,StartBit);
|
||||
int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
|
||||
int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
|
||||
int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
|
||||
int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
|
||||
|
||||
int seq_scaling_list_present_flag[8];
|
||||
if( seq_scaling_matrix_present_flag )
|
||||
{
|
||||
for( int i = 0; i < 8; i++ ) {
|
||||
seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
|
||||
int pic_order_cnt_type=Ue(buf,nLen,StartBit);
|
||||
if( pic_order_cnt_type == 0 )
|
||||
int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
|
||||
else if( pic_order_cnt_type == 1 )
|
||||
{
|
||||
int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
|
||||
int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
|
||||
int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
|
||||
int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
|
||||
|
||||
int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
|
||||
for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
|
||||
offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
|
||||
delete [] offset_for_ref_frame;
|
||||
}
|
||||
int num_ref_frames=Ue(buf,nLen,StartBit);
|
||||
int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
|
||||
int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
|
||||
int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
|
||||
|
||||
width=(pic_width_in_mbs_minus1+1)*16;
|
||||
height=(pic_height_in_map_units_minus1+1)*16;
|
||||
|
||||
int frame_mbs_only_flag=u(1,buf,StartBit);
|
||||
if(!frame_mbs_only_flag)
|
||||
int mb_adaptive_frame_field_flag=u(1,buf,StartBit);
|
||||
|
||||
int direct_8x8_inference_flag=u(1,buf,StartBit);
|
||||
int frame_cropping_flag=u(1,buf,StartBit);
|
||||
if(frame_cropping_flag)
|
||||
{
|
||||
int frame_crop_left_offset=Ue(buf,nLen,StartBit);
|
||||
int frame_crop_right_offset=Ue(buf,nLen,StartBit);
|
||||
int frame_crop_top_offset=Ue(buf,nLen,StartBit);
|
||||
int frame_crop_bottom_offset=Ue(buf,nLen,StartBit);
|
||||
}
|
||||
int vui_parameter_present_flag=u(1,buf,StartBit);
|
||||
if(vui_parameter_present_flag)
|
||||
{
|
||||
int aspect_ratio_info_present_flag=u(1,buf,StartBit);
|
||||
if(aspect_ratio_info_present_flag)
|
||||
{
|
||||
int aspect_ratio_idc=u(8,buf,StartBit);
|
||||
if(aspect_ratio_idc==255)
|
||||
{
|
||||
int sar_width=u(16,buf,StartBit);
|
||||
int sar_height=u(16,buf,StartBit);
|
||||
}
|
||||
}
|
||||
int overscan_info_present_flag=u(1,buf,StartBit);
|
||||
if(overscan_info_present_flag)
|
||||
int overscan_appropriate_flagu=u(1,buf,StartBit);
|
||||
int video_signal_type_present_flag=u(1,buf,StartBit);
|
||||
if(video_signal_type_present_flag)
|
||||
{
|
||||
int video_format=u(3,buf,StartBit);
|
||||
int video_full_range_flag=u(1,buf,StartBit);
|
||||
int colour_description_present_flag=u(1,buf,StartBit);
|
||||
if(colour_description_present_flag)
|
||||
{
|
||||
int colour_primaries=u(8,buf,StartBit);
|
||||
int transfer_characteristics=u(8,buf,StartBit);
|
||||
int matrix_coefficients=u(8,buf,StartBit);
|
||||
}
|
||||
}
|
||||
int chroma_loc_info_present_flag=u(1,buf,StartBit);
|
||||
if(chroma_loc_info_present_flag)
|
||||
{
|
||||
int chroma_sample_loc_type_top_field=Ue(buf,nLen,StartBit);
|
||||
int chroma_sample_loc_type_bottom_field=Ue(buf,nLen,StartBit);
|
||||
}
|
||||
int timing_info_present_flag=u(1,buf,StartBit);
|
||||
if(timing_info_present_flag)
|
||||
{
|
||||
int num_units_in_tick=u(32,buf,StartBit);
|
||||
int time_scale=u(32,buf,StartBit);
|
||||
fps=time_scale/(2*num_units_in_tick);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef __SPS_DECODE__
|
||||
#define __SPS_DECODE__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
|
||||
UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit);
|
||||
|
||||
int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit);
|
||||
DWORD u(UINT BitCount, BYTE * buf, UINT &nStartBit);
|
||||
/**
|
||||
* H264的NAL起始码防竞争机制
|
||||
*
|
||||
* @param buf SPS数据内容
|
||||
*
|
||||
* @无返回值
|
||||
*/
|
||||
void de_emulation_prevention(BYTE* buf, unsigned int* buf_size);
|
||||
/**
|
||||
* 解码SPS,获取视频图像宽、高信息
|
||||
*
|
||||
* @param buf SPS数据内容
|
||||
* @param nLen SPS数据的长度
|
||||
* @param width 图像宽度
|
||||
* @param height 图像高度
|
||||
|
||||
* @成功则返回1 , 失败则返回0
|
||||
*/
|
||||
int h264_decode_sps(BYTE * buf, unsigned int nLen, int &width, int &height, int &fps);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
#include <Unknwn.h>
|
||||
#include <strmif.h>
|
||||
|
||||
#pragma comment(lib, "strmiids.lib")
|
||||
|
||||
#ifndef __qedit_h__
|
||||
#define __qedit_h__
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct __declspec(uuid("0579154a-2b53-4994-b0d0-e773148eff85"))
|
||||
ISampleGrabberCB : IUnknown
|
||||
{
|
||||
//
|
||||
// Raw methods provided by interface
|
||||
//
|
||||
|
||||
virtual HRESULT __stdcall SampleCB(
|
||||
double SampleTime,
|
||||
struct IMediaSample * pSample) = 0;
|
||||
virtual HRESULT __stdcall BufferCB(
|
||||
double SampleTime,
|
||||
unsigned char * pBuffer,
|
||||
long BufferLen) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
struct __declspec(uuid("6b652fff-11fe-4fce-92ad-0266b5d7c78f"))
|
||||
ISampleGrabber : IUnknown
|
||||
{
|
||||
//
|
||||
// Raw methods provided by interface
|
||||
//
|
||||
|
||||
virtual HRESULT __stdcall SetOneShot(
|
||||
long OneShot) = 0;
|
||||
virtual HRESULT __stdcall SetMediaType(
|
||||
struct _AMMediaType * pType) = 0;
|
||||
virtual HRESULT __stdcall GetConnectedMediaType(
|
||||
struct _AMMediaType * pType) = 0;
|
||||
virtual HRESULT __stdcall SetBufferSamples(
|
||||
long BufferThem) = 0;
|
||||
virtual HRESULT __stdcall GetCurrentBuffer(
|
||||
/*[in,out]*/ long * pBufferSize,
|
||||
/*[out]*/ long * pBuffer) = 0;
|
||||
virtual HRESULT __stdcall GetCurrentSample(
|
||||
/*[out,retval]*/ struct IMediaSample * * ppSample) = 0;
|
||||
virtual HRESULT __stdcall SetCallback(
|
||||
struct ISampleGrabberCB * pCallback,
|
||||
long WhichMethodToCallback) = 0;
|
||||
};
|
||||
|
||||
|
||||
static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce,{ 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
|
||||
static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994,{ 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
|
||||
static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3,{ 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3,{ 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
### 基于qt本地PC摄像头采集并264 编码rtmp推流
|
||||
|
||||
#### 支持平台
|
||||
1. windows
|
||||
|
||||
#### 依赖库:
|
||||
1. 使用librtmp提供的接口实现rtmp推流。
|
||||
2. ffmpeg作为视频264编码。
|
||||
3. opengl用于本地摄像头的渲染。
|
||||
4. directshow的相关接口用于获取本地的摄像头数据。
|
||||
5. 界面风格基于!qssWrpter库 https://gitee.com/290198252/qsswraper
|
||||
|
||||
|
||||
### 界面:
|
||||
![image.png](https://www.testingcloud.club/sapi/api/image_download/80463174-3633-11eb-a135-525400dc6cec.png)
|
||||
|
||||
#### 支持的摄像头
|
||||
|
||||
|
||||
#### 发行版
|
||||
- win32
|
|
@ -0,0 +1,161 @@
|
|||
/********************************************************************************
|
||||
** Form generated from reading UI file 'mainwindow.ui'
|
||||
**
|
||||
** Created by: Qt User Interface Compiler version 5.14.0
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef UI_MAINWINDOW_H
|
||||
#define UI_MAINWINDOW_H
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QSpacerItem>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Ui_MainWindow
|
||||
{
|
||||
public:
|
||||
QWidget *centralWidget;
|
||||
QGridLayout *gridLayout;
|
||||
QVBoxLayout *verticalLayout;
|
||||
QHBoxLayout *horizontalLayout;
|
||||
QPushButton *pushButton;
|
||||
QComboBox *comboBox;
|
||||
QPushButton *pushButton_3;
|
||||
QComboBox *comboBox_2;
|
||||
QLabel *label;
|
||||
QLineEdit *lineEdit;
|
||||
QPushButton *pushButton_2;
|
||||
QSpacerItem *horizontalSpacer_2;
|
||||
QSpacerItem *verticalSpacer;
|
||||
|
||||
void setupUi(QMainWindow *MainWindow)
|
||||
{
|
||||
if (MainWindow->objectName().isEmpty())
|
||||
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
|
||||
MainWindow->resize(1383, 1116);
|
||||
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
sizePolicy.setHorizontalStretch(0);
|
||||
sizePolicy.setVerticalStretch(0);
|
||||
sizePolicy.setHeightForWidth(MainWindow->sizePolicy().hasHeightForWidth());
|
||||
MainWindow->setSizePolicy(sizePolicy);
|
||||
MainWindow->setMinimumSize(QSize(600, 800));
|
||||
centralWidget = new QWidget(MainWindow);
|
||||
centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
|
||||
QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
sizePolicy1.setHorizontalStretch(0);
|
||||
sizePolicy1.setVerticalStretch(0);
|
||||
sizePolicy1.setHeightForWidth(centralWidget->sizePolicy().hasHeightForWidth());
|
||||
centralWidget->setSizePolicy(sizePolicy1);
|
||||
gridLayout = new QGridLayout(centralWidget);
|
||||
gridLayout->setSpacing(6);
|
||||
gridLayout->setContentsMargins(11, 11, 11, 11);
|
||||
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
|
||||
verticalLayout = new QVBoxLayout();
|
||||
verticalLayout->setSpacing(6);
|
||||
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
|
||||
horizontalLayout = new QHBoxLayout();
|
||||
horizontalLayout->setSpacing(6);
|
||||
horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
|
||||
horizontalLayout->setContentsMargins(2, 2, 2, 1);
|
||||
pushButton = new QPushButton(centralWidget);
|
||||
pushButton->setObjectName(QString::fromUtf8("pushButton"));
|
||||
pushButton->setMinimumSize(QSize(100, 50));
|
||||
pushButton->setSizeIncrement(QSize(0, 6));
|
||||
pushButton->setBaseSize(QSize(0, 50));
|
||||
|
||||
horizontalLayout->addWidget(pushButton);
|
||||
|
||||
comboBox = new QComboBox(centralWidget);
|
||||
comboBox->setObjectName(QString::fromUtf8("comboBox"));
|
||||
comboBox->setMinimumSize(QSize(200, 35));
|
||||
comboBox->setBaseSize(QSize(0, 50));
|
||||
|
||||
horizontalLayout->addWidget(comboBox);
|
||||
|
||||
pushButton_3 = new QPushButton(centralWidget);
|
||||
pushButton_3->setObjectName(QString::fromUtf8("pushButton_3"));
|
||||
pushButton_3->setMinimumSize(QSize(100, 50));
|
||||
|
||||
horizontalLayout->addWidget(pushButton_3);
|
||||
|
||||
comboBox_2 = new QComboBox(centralWidget);
|
||||
comboBox_2->setObjectName(QString::fromUtf8("comboBox_2"));
|
||||
comboBox_2->setMinimumSize(QSize(200, 35));
|
||||
|
||||
horizontalLayout->addWidget(comboBox_2);
|
||||
|
||||
label = new QLabel(centralWidget);
|
||||
label->setObjectName(QString::fromUtf8("label"));
|
||||
|
||||
horizontalLayout->addWidget(label);
|
||||
|
||||
lineEdit = new QLineEdit(centralWidget);
|
||||
lineEdit->setObjectName(QString::fromUtf8("lineEdit"));
|
||||
lineEdit->setMinimumSize(QSize(300, 30));
|
||||
|
||||
horizontalLayout->addWidget(lineEdit);
|
||||
|
||||
pushButton_2 = new QPushButton(centralWidget);
|
||||
pushButton_2->setObjectName(QString::fromUtf8("pushButton_2"));
|
||||
pushButton_2->setMinimumSize(QSize(60, 50));
|
||||
|
||||
horizontalLayout->addWidget(pushButton_2);
|
||||
|
||||
horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
|
||||
horizontalLayout->addItem(horizontalSpacer_2);
|
||||
|
||||
horizontalLayout->setStretch(0, 1);
|
||||
horizontalLayout->setStretch(1, 2);
|
||||
horizontalLayout->setStretch(7, 13);
|
||||
|
||||
verticalLayout->addLayout(horizontalLayout);
|
||||
|
||||
verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
|
||||
verticalLayout->addItem(verticalSpacer);
|
||||
|
||||
verticalLayout->setStretch(0, 1);
|
||||
verticalLayout->setStretch(1, 9);
|
||||
|
||||
gridLayout->addLayout(verticalLayout, 0, 0, 1, 1);
|
||||
|
||||
MainWindow->setCentralWidget(centralWidget);
|
||||
|
||||
retranslateUi(MainWindow);
|
||||
|
||||
QMetaObject::connectSlotsByName(MainWindow);
|
||||
} // setupUi
|
||||
|
||||
void retranslateUi(QMainWindow *MainWindow)
|
||||
{
|
||||
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
|
||||
pushButton->setText(QCoreApplication::translate("MainWindow", "\346\211\223\345\274\200\346\221\204\345\203\217\345\244\264", nullptr));
|
||||
pushButton_3->setText(QCoreApplication::translate("MainWindow", "\346\211\223\345\274\200\351\272\246\345\205\213\351\243\216", nullptr));
|
||||
label->setText(QCoreApplication::translate("MainWindow", "rtmp\346\216\250\346\265\201\345\234\260\345\235\200", nullptr));
|
||||
lineEdit->setText(QCoreApplication::translate("MainWindow", "rtmp://127.0.0.1:1935/live/1", nullptr));
|
||||
pushButton_2->setText(QCoreApplication::translate("MainWindow", "\346\216\250\346\265\201", nullptr));
|
||||
} // retranslateUi
|
||||
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow: public Ui_MainWindow {};
|
||||
} // namespace Ui
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // UI_MAINWINDOW_H
|
|
@ -0,0 +1,63 @@
|
|||
/********************************************************************************
|
||||
** Form generated from reading UI file 'process.ui'
|
||||
**
|
||||
** Created by: Qt User Interface Compiler version 5.14.0
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef UI_PROCESS_H
|
||||
#define UI_PROCESS_H
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QProgressBar>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Ui_Process
|
||||
{
|
||||
public:
|
||||
QProgressBar *progressBar;
|
||||
QLabel *label;
|
||||
|
||||
void setupUi(QDialog *Process)
|
||||
{
|
||||
if (Process->objectName().isEmpty())
|
||||
Process->setObjectName(QString::fromUtf8("Process"));
|
||||
Process->resize(324, 88);
|
||||
QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
sizePolicy.setHorizontalStretch(0);
|
||||
sizePolicy.setVerticalStretch(0);
|
||||
sizePolicy.setHeightForWidth(Process->sizePolicy().hasHeightForWidth());
|
||||
Process->setSizePolicy(sizePolicy);
|
||||
progressBar = new QProgressBar(Process);
|
||||
progressBar->setObjectName(QString::fromUtf8("progressBar"));
|
||||
progressBar->setGeometry(QRect(30, 50, 281, 31));
|
||||
progressBar->setValue(24);
|
||||
label = new QLabel(Process);
|
||||
label->setObjectName(QString::fromUtf8("label"));
|
||||
label->setGeometry(QRect(120, 30, 121, 16));
|
||||
|
||||
retranslateUi(Process);
|
||||
|
||||
QMetaObject::connectSlotsByName(Process);
|
||||
} // setupUi
|
||||
|
||||
void retranslateUi(QDialog *Process)
|
||||
{
|
||||
Process->setWindowTitle(QCoreApplication::translate("Process", "Dialog", nullptr));
|
||||
label->setText(QCoreApplication::translate("Process", "\346\255\243\345\234\250\345\212\240\350\275\275\346\225\260\346\215\256", nullptr));
|
||||
} // retranslateUi
|
||||
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class Process: public Ui_Process {};
|
||||
} // namespace Ui
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // UI_PROCESS_H
|
|
@ -0,0 +1,55 @@
|
|||
/********************************************************************************
|
||||
** Form generated from reading UI file 'qsstoast.ui'
|
||||
**
|
||||
** Created by: Qt User Interface Compiler version 5.14.0
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef UI_QSSTOAST_H
|
||||
#define UI_QSSTOAST_H
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Ui_Toast
|
||||
{
|
||||
public:
|
||||
QLabel *label;
|
||||
|
||||
void setupUi(QWidget *Toast)
|
||||
{
|
||||
if (Toast->objectName().isEmpty())
|
||||
Toast->setObjectName(QString::fromUtf8("Toast"));
|
||||
Toast->resize(932, 59);
|
||||
QFont font;
|
||||
font.setFamily(QString::fromUtf8("Arial"));
|
||||
Toast->setFont(font);
|
||||
label = new QLabel(Toast);
|
||||
label->setObjectName(QString::fromUtf8("label"));
|
||||
label->setGeometry(QRect(170, 10, 231, 31));
|
||||
|
||||
retranslateUi(Toast);
|
||||
|
||||
QMetaObject::connectSlotsByName(Toast);
|
||||
} // setupUi
|
||||
|
||||
void retranslateUi(QWidget *Toast)
|
||||
{
|
||||
Toast->setWindowTitle(QCoreApplication::translate("Toast", "Form", nullptr));
|
||||
label->setText(QCoreApplication::translate("Toast", "TextLabel", nullptr));
|
||||
} // retranslateUi
|
||||
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class Toast: public Ui_Toast {};
|
||||
} // namespace Ui
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // UI_QSSTOAST_H
|
|
@ -0,0 +1,52 @@
|
|||
/********************************************************************************
|
||||
** Form generated from reading UI file 'toast.ui'
|
||||
**
|
||||
** Created by: Qt User Interface Compiler version 5.14.0
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef UI_TOAST_H
|
||||
#define UI_TOAST_H
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Ui_Form
|
||||
{
|
||||
public:
|
||||
QLabel *label;
|
||||
|
||||
void setupUi(QWidget *Form)
|
||||
{
|
||||
if (Form->objectName().isEmpty())
|
||||
Form->setObjectName(QString::fromUtf8("Form"));
|
||||
Form->resize(932, 59);
|
||||
label = new QLabel(Form);
|
||||
label->setObjectName(QString::fromUtf8("label"));
|
||||
label->setGeometry(QRect(170, 10, 231, 31));
|
||||
|
||||
retranslateUi(Form);
|
||||
|
||||
QMetaObject::connectSlotsByName(Form);
|
||||
} // setupUi
|
||||
|
||||
void retranslateUi(QWidget *Form)
|
||||
{
|
||||
Form->setWindowTitle(QCoreApplication::translate("Form", "Form", nullptr));
|
||||
label->setText(QCoreApplication::translate("Form", "TextLabel", nullptr));
|
||||
} // retranslateUi
|
||||
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class Form: public Ui_Form {};
|
||||
} // namespace Ui
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // UI_TOAST_H
|
|
@ -0,0 +1,119 @@
|
|||
#include "Base64.h"
|
||||
#include "string.h"
|
||||
int DecodeBase64(char * pInput, char * pOutput) {
|
||||
int i = 0;
|
||||
int iCnt = 0;
|
||||
int iSrcLen = (int)strlen(pInput);
|
||||
|
||||
char * p = pOutput;
|
||||
|
||||
for (i=0; i<iSrcLen; i++)
|
||||
{
|
||||
if (pInput[i] > 127) continue;
|
||||
if (pInput[i] == '=') return p-pOutput+1;
|
||||
|
||||
char a = BVal(pInput[i]);
|
||||
if (a == 255) continue;
|
||||
|
||||
switch (iCnt)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
*p = a << 2;
|
||||
iCnt++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
*p++ |= a >> 4;
|
||||
*p = a << 4;
|
||||
iCnt++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
*p++ |= a >> 2;
|
||||
*p = a << 6;
|
||||
iCnt++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
*p++ |= a;
|
||||
iCnt = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*p = 0x00;
|
||||
return p-pOutput;
|
||||
}
|
||||
|
||||
int EncodeBase64(char * pInput,int iInputLen,char * pOutput)
|
||||
{
|
||||
int i = 0;
|
||||
int loop = 0;
|
||||
int remain = 0;
|
||||
int iDstLen = 0;
|
||||
int iSrcLen = iInputLen;
|
||||
|
||||
loop = iSrcLen/3;
|
||||
remain = iSrcLen%3;
|
||||
|
||||
// also can encode native char one by one as decode method
|
||||
// but because all of char in native string is to be encoded so encode 3-chars one time is easier.
|
||||
|
||||
for (i=0; i<loop; i++)
|
||||
{
|
||||
char a1 = (pInput[i*3] >> 2);
|
||||
char a2 = ( ((pInput[i*3] & 0x03) << 4) | (pInput[i*3+1] >> 4) );
|
||||
char a3 = ( ((pInput[i*3+1] & 0x0F) << 2) | ((pInput[i*3+2] & 0xC0) >> 6) );
|
||||
char a4 = (pInput[i*3+2] & 0x3F);
|
||||
|
||||
pOutput[i*4] = AVal(a1);
|
||||
pOutput[i*4+1] = AVal(a2);
|
||||
pOutput[i*4+2] = AVal(a3);
|
||||
pOutput[i*4+3] = AVal(a4);
|
||||
}
|
||||
|
||||
iDstLen = i*4;
|
||||
|
||||
if (remain == 1)
|
||||
{
|
||||
// should pad two equal sign
|
||||
i = iSrcLen-1;
|
||||
char a1 = (pInput[i] >> 2);
|
||||
char a2 = ((pInput[i] & 0x03) << 4);
|
||||
|
||||
pOutput[iDstLen++] = AVal(a1);
|
||||
pOutput[iDstLen++] = AVal(a2);
|
||||
pOutput[iDstLen++] = '=';
|
||||
pOutput[iDstLen++] = '=';
|
||||
pOutput[iDstLen] = 0x00;
|
||||
}
|
||||
else if (remain == 2)
|
||||
{
|
||||
// should pad one equal sign
|
||||
i = iSrcLen-2;
|
||||
char a1 = (pInput[i] >> 2);
|
||||
char a2 = ( ((pInput[i] & 0x03) << 4) | (pInput[i+1] >> 4));
|
||||
char a3 = ( (pInput[i+1] & 0x0F) << 2);
|
||||
|
||||
pOutput[iDstLen++] = AVal(a1);
|
||||
pOutput[iDstLen++] = AVal(a2);
|
||||
pOutput[iDstLen++] = AVal(a3);
|
||||
pOutput[iDstLen++] = '=';
|
||||
pOutput[iDstLen] = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
// just division by 3
|
||||
pOutput[iDstLen] = 0x00;
|
||||
}
|
||||
|
||||
return iDstLen;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#include "Debuger.h"
|
||||
|
||||
|
||||
Debuger::Debuger() {
|
||||
}
|
||||
|
||||
Debuger::~Debuger() {
|
||||
}
|
||||
|
||||
int Debuger::Debug(wstring log) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Debuger::Debug(string log) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Debuger::Debug(const wchar_t *format, ...) {
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#include "utils.h"
|
||||
|
||||
|
||||
wstring char2wchar(const char* cchar)
|
||||
{
|
||||
/*
|
||||
wchar_t m_wchar[2000];
|
||||
int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
|
||||
if (len > 0)
|
||||
{
|
||||
MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
|
||||
m_wchar[len] = L'\0';
|
||||
return std::wstring(m_wchar);
|
||||
}*/
|
||||
return wstring(L"");
|
||||
}
|
||||
|
||||
|
||||
AVPixelFormat GUIDToAvFormat(GUID mediatype){
|
||||
if(IsEqualIID(MEDIASUBTYPE_RGB32,mediatype)){
|
||||
return AV_PIX_FMT_BGRA;
|
||||
}
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2019-09-23T11:02:49
|
||||
#
|
||||
#-------------------------------------------------
|
||||
QT += core gui
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
QT += network
|
||||
QT += multimedia
|
||||
TARGET = yuvgl
|
||||
|
||||
|
||||
INCLUDEPATH += $$[QT_INSTALL_HEADERS]/QtZlib
|
||||
|
||||
include(G:\\project\\c++qt\\qsswraper\\qsswraper.pri)
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
CONFIG += C++11
|
||||
|
||||
DEFINES += WIN32_LEAN_AND_MEAN
|
||||
|
||||
SOURCES += \
|
||||
components/toast.cpp \
|
||||
librtmp/amf.c \
|
||||
librtmp/hashswf.c \
|
||||
librtmp/log.c \
|
||||
librtmp/parseurl.c \
|
||||
librtmp/rtmp.c \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
cplaywidget.cpp \
|
||||
media/AACAudioCoder.cpp \
|
||||
media/AudioCapture.cpp \
|
||||
media/CameraCapture.cpp \
|
||||
media/DXGICapture.cpp \
|
||||
media/RtmpPusher.cpp \
|
||||
media/VideoCoder.cpp \
|
||||
media/audiocaptureff.cpp \
|
||||
media/screen_capture.cpp \
|
||||
media/sps_decode.cpp \
|
||||
utils/Base64.cpp \
|
||||
utils/Debuger.cpp \
|
||||
utils/utils.cpp
|
||||
HEADERS += \
|
||||
components/toast.h \
|
||||
librtmp/strncasecmp.h \
|
||||
mainwindow.h \
|
||||
cplaywidget.h \
|
||||
media/screen_capture.h
|
||||
|
||||
|
||||
FORMS += \
|
||||
components/toast.ui \
|
||||
mainwindow.ui
|
||||
|
||||
|
||||
INCLUDEPATH += media/ \
|
||||
C:\\Program Files\\OpenSSL-Win64\\include
|
||||
|
||||
contains(DEFINES, __MINGW32__){
|
||||
message("mingw")
|
||||
INCLUDEPATH += media/ inc/
|
||||
contains(QT_ARCH, i386) {
|
||||
message("32-bit")
|
||||
LIBS += -L$$PWD/third/ffmpeg/mingw/32/lib
|
||||
LIBS += -lm -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswresample -lswscale -lpthread -lm -lfdk-aac -lx264 -liconv -lucrtbase -lstrmiids
|
||||
LIBS += -lole32 -loleAut32 -lquartz -ldxguid -ldxapi -lwinmm -lbcrypt -lssl -lcrypto -lGdi32 -lws2_32 -lbz2 -lz -lportaudio -lshlwapi -lvfw32 -lpostproc -luuid
|
||||
} else {
|
||||
message("64-bit")
|
||||
}
|
||||
}else{
|
||||
message("msvc")
|
||||
|
||||
|
||||
DEFINES += _CRT_SECURE_NO_DEPRECATE \
|
||||
_CRT_NONSTDC_NO_DEPRECATE
|
||||
|
||||
|
||||
contains(QT_ARCH, i386) {
|
||||
INCLUDEPATH += inc $$PWD/third/msvc32/fdk-aac/include \
|
||||
$$PWD/third/msvc32/libx264/include \
|
||||
$$PWD/third/msvc32/ffmpeg/include \
|
||||
$$PWD/third/msvc32/openssl/include
|
||||
|
||||
LIBS += -L$$PWD/third/msvc32/libx264/lib
|
||||
LIBS += -L$$PWD/third/msvc32/fdk-aac/lib
|
||||
LIBS += -L$$PWD/third/msvc32/ffmpeg/lib
|
||||
LIBS += -L$$PWD/third/msvc32/openssl/lib
|
||||
|
||||
LIBS += libavfilter.a libavdevice.a libavcodec.a libpostproc.a \
|
||||
libavformat.a libavutil.a \
|
||||
libswresample.a libswscale.a fdk-aac.lib ws2_32.lib libeay32.lib ssleay32.lib \
|
||||
shell32.lib gdi32.lib crypt32.lib User32.lib GDI32.lib Advapi32.lib zlibstaticd.lib Secur32.lib \
|
||||
Bcrypt.lib Kernel32.lib portaudio_x86.lib ole32.lib oleaut32.lib strmiids.lib libx264.lib d3d9.lib
|
||||
|
||||
}
|
||||
else{
|
||||
message("64-bit")
|
||||
|
||||
QMAKE_CXXFLAGS_RELEASE += -Zi
|
||||
QMAKE_LFLAGS_RELEASE += /DEBUG /OPT:REF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 11.0.3, 2023-11-12T20:47:56. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
<value type="QByteArray">{2f8149c7-6065-4889-8af2-fdde6a16f5dc}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
<value type="qlonglong">0</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.EditorSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
|
||||
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
|
||||
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
|
||||
<value type="QString" key="language">Cpp</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
|
||||
<value type="QString" key="language">QmlJS</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
|
||||
<value type="QByteArray" key="EditorConfiguration.Codec">GBK</value>
|
||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.IndentSize">4</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
|
||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
|
||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
|
||||
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
|
||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">2</value>
|
||||
<value type="int" key="EditorConfiguration.TabSize">8</value>
|
||||
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
|
||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
|
||||
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
|
||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.PluginSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
|
||||
<value type="bool" key="AutoTest.Framework.Boost">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.CTest">false</value>
|
||||
<value type="bool" key="AutoTest.Framework.Catch">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.GTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
|
||||
<value type="int" key="AutoTest.RunAfterBuild">0</value>
|
||||
<value type="bool" key="AutoTest.UseGlobal">true</value>
|
||||
<valuemap type="QVariantMap" key="ClangTools">
|
||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">6</value>
|
||||
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
|
||||
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Target.0</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="QString" key="DeviceType">Desktop</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.15.2 MSVC2019 64bit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.15.2 MSVC2019 64bit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5152.win64_msvc2019_64_kit</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="int" key="EnableQmlDebugging">0</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">G:\project\multimedia\client\rtmp_demo\build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Debug</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">G:/project/multimedia/client/rtmp_demo/build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Debug</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
|
||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
|
||||
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
|
||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">G:\project\multimedia\client\rtmp_demo\build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Release</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">G:/project/multimedia/client/rtmp_demo/build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Release</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
|
||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
|
||||
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
|
||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
|
||||
<value type="int" key="QtQuickCompiler">0</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
|
||||
<value type="int" key="EnableQmlDebugging">0</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">G:\project\multimedia\client\rtmp_demo\build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Profile</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">G:/project/multimedia/client/rtmp_demo/build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Profile</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
|
||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
|
||||
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
|
||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
|
||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
|
||||
<value type="int" key="QtQuickCompiler">0</value>
|
||||
<value type="int" key="SeparateDebugInfo">0</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:G:/project/multimedia/client/rtmp_demo/yuvgl/yuvgl.pro</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">G:/project/multimedia/client/rtmp_demo/yuvgl/yuvgl.pro</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">G:/project/multimedia/client/rtmp_demo/build-yuvgl-Desktop_Qt_5_15_2_MSVC2019_64bit-Debug</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.TargetCount</variable>
|
||||
<value type="qlonglong">1</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>Version</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
</qtcreator>
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue