Posted on 01-06-2015
Filed Under (技术) by waterlin

以下示例声明了一个指向 float 的指针:

float *powerPtr;

因为 powerPtr 变量的类型是指向 float 的指针,所以读者可能会将代码写成:

float* powerPtr;

这样写没有问题,编译也会通过,但不是好的代码风格。

C语言允许在一行代码中声明多个变量。例如,要声明变量 x、y和 z,可以将代码写成:

float x, y, z;

以上三个变量的类型都是 float。

再看下面这段代码,b 和 c 变量分别是什么类型?

float* b, c;

答案可能会出乎读者的意料。b 是指向 float 的指针,但 c 的类型却是 float。如果需要将 a 和 b 都声明为指针,就必须在每个变量前都加上*:

float *b, *c;

在这种情况下,将 * 写在变量名这边,能让声明看上去更清楚。

摘录自《Object-C 编程》122页。

(0) Comments    Read More   
Posted on 04-01-2015
Filed Under (技术) by waterlin

最近在使用定时器的时候,被一个小细节坑了,偶尔导致 coredump,费了好大的力气才找到原因,现在整理一下备忘。

我采用了如下方式来生成了定时器:

if (!CreateTimerQueueTimer( &m_hTimer, m_hTimerQueue, 
    (WAITORTIMERCALLBACK)videoTimer_proc, this, interval, 0, 0))
{
    printf("CreateTimerQueueTimer failed (%d)", GetLastError());
    return false;
}

然后,我就不断地调用上面的代码来生成定时器,并干相应的的事情,比如说视频帧的刷新等操作。但是,我犯了一个错误,在我需要结束定时操作后,我采用了如下的方式来结束定时器:

if (m_hTimer)
{
    DeleteTimerQueueTimer(m_hTimerQueue, m_hTimer, NULL);
    m_hTimer = NULL;
}

就是因为最后一个参数的原因,偶尔会出现崩溃:即定时器并没有因为这一行代码而完全销毁,从而导致这个类在销毁后,定时器依然触发了回调信息。

要解决这个问题很简单,只需要把上述代码里的 NULL 参数修改为 INVALID_HANDLE_VALUE 即可:

if (m_hTimer)
{
    DeleteTimerQueueTimer(m_hTimerQueue, m_hTimer, INVALID_HANDLE_VALUE);
    m_hTimer = NULL;
}

看看微软官方文档对这两个参数区别的说明:

If this parameter is INVALID_HANDLE_VALUE, the function waits for any running timer callback functions to complete before returning.

If this parameter is NULL, the function marks the timer for deletion and returns immediately. If the timer has already expired, the timer callback function will run to completion. However, there is no notification sent when the timer callback function has completed. Most callers should not use this option, and should wait for running timer callback functions to complete so they can perform any needed cleanup.

即,如果使用 INVALID_HANDLE_VALUE 这个参数,则 DeleteTimerQueueTimer 会等待定时器销毁以及回调函数结束后才返回;如果使用 NULL 则把定时器立即标记为销毁,但是实际上的运行并不受控制,如果有定时器正处在回调函数里并正在执行,有可能导致异常情况。

我的崩溃就是由于这个参数引起的,不容易出现,很不好找原因。

(1) Comment    Read More   
Posted on 05-11-2014
Filed Under (技术) by waterlin

今天在使用 FFMpeg 解码 iPad 过来的视频流时,有一个非常奇怪的现象,即对于分辨率为 406*720 和 450*720 的图像来说,解码一切正常,但是把 YUV420 的数据转为 RGBA 显示出来时,图像的最右边会有一条几个像素的黑竖条,如下图所示:

./images/ffmpeg-black-bar2.jpg

碰到这个问题,通常的解决思路如下。

第一,检查一下解码是否有错误,这个好办,只要解码不返回错误码就是正常。

第二,如果解码没有问题的话,仔细查看各步的图片格式转化操作是否有问题。

1. 先看看解码出来的 yuv 数据是否就有这条黑边。可以直接把 yuv 保存成二进制文件,然后用 yuvplayer 播放。

如果是使用 FFMpeg 解码,则可以使用如下的代码来保存 yuv (平面格式)数据:

//保存yuv数据
static int fcount = 0;
char yuvdumpName[255];

sprintf(yuvdumpName, "E:/temp/yuv/%d.yuv", fcount);
FILE *fp = fopen(yuvdumpName, "ab");
if (fp) {

    char* yuv = (char*)src_frame->data[0];
    int stride = src_frame->linesize[0];

    for(int y=0; y<720; y++) {
        fwrite(yuv+stride*y, 1, 540, fp);
    }

    char* udata = (char*)src_frame->data[1];
    int ustride = src_frame->linesize[1];

    for(int u=0; u<720/2; u++) {
        fwrite(udata+u*ustride, 1, 540/2, fp);
    }

    char* vdata = (char*)src_frame->data[2];
    int vstride = src_frame->linesize[2];
    for(int v=0; v<720/2; v++) {
        fwrite(vdata+v*vstride, 1, 540/2, fp);
    }

    fclose(fp);
    fcount++;
}

在我这里有一个特别奇怪的现象,分辨是 540*720,但是解码后得到的 YUV 的 stridesize 是: 576, 288, 288。经过查找资料,原来为了方便各种格式转换及效率,通常是把分辨率的宽度进行了 16 倍整数化,方便硬件加速。

在我查看了 yuv 图片后,还是一切正常,真是怪事,只能继续找原因了。

2. 既然 yuv 数据没有黑边,则把 sws_scale 转成 BGRA 数据后的图片保存成位图,看是否有黑边。

可以使用如下的代码来保存 BGRA 的数据:

void saveBmpFile(uint8_t *src,int srcW,int srcH,int srcStride)
{
    static int fileIndex = 0;

    char filename[256];
    sprintf(filename,"e:\\temp\\yuv\\bmp\\%d.bmp", fileIndex++);
    BITMAPINFOHEADER bih; 
    BITMAPFILEHEADER bhh;  
    int widthStep = srcStride; //(((src->m_width * 24) + 31) & (~31)) / 8 ;  
    bih.biSize=40;       // header size  
    bih.biWidth= srcW;
    bih.biHeight= abs(srcH);
    bih.biPlanes=1;  
    bih.biBitCount=32;     // RGB encoded, 24 bit  
    bih.biCompression=BI_RGB;   // no compression
    bih.biSizeImage=srcStride*abs(srcH);
    bih.biXPelsPerMeter=0;  
    bih.biYPelsPerMeter=0;  
    bih.biClrUsed=0;  
    bih.biClrImportant=0;   
    bhh.bfType = ((WORD) ('M' << 8) | 'B');  //'BM'  
    bhh.bfSize = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + widthStep * abs(srcH);
    bhh.bfReserved1 = 0;  
    bhh.bfReserved2 = 0;  
    bhh.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    FILE *fp = fopen(filename, "wb");
    if( fp ) {
        src = src + (abs(srcH)-1)*srcStride;
        fwrite(&bhh,1,sizeof(bhh),fp);
        fwrite(&bih,1,sizeof(bih),fp);
        for(int y=0; y<abs(srcH); y++) {
            fwrite(src,1,srcW*4,fp);
            src -= srcStride;
        }
        fclose(fp);
    }
}

如果使用了 Qt 库,也可以直接这样保存图片:

AVFrame *pFrameRGB = avcodec_alloc_frame();

//some decode operation

static int index = 0;
QImage image( pFrameRGB->data[0], codec->width, codec->height, QImage::Format_RGB32 );
image = image.copy();
image.save(QString("E:/temp/yuv/jpg/%1.jpg").arg(index));
index++;

在我这里,恰恰是第2步使用 sws_scale 转换图片格式后出现的黑边。我原来 sws_scale 的使用方法如下:

SwsContext *img_convert_ctx_temp;
img_convert_ctx_temp = sws_getContext(
                                    codec->width, codec->height,
                                    codec->pix_fmt,
                                    dst_w, dst_h, (PixelFormat)dst_fmt,
                                    SWS_BICUBIC, NULL, NULL, NULL);

sws_scale(img_convert_ctx_temp,
          src_frame->data, src_frame->linesize, 0, codec->height,
          pFrameRGB->data,
          pFrameRGB->linesize);

事实上问题就出在这里,是 sws_getContext 这个函数的参数需要带上 SWS_ACCURATE_RND,即修改成如下方式:

SwsContext *img_convert_ctx_temp;
img_convert_ctx_temp = sws_getContext(
                                    codec->width, codec->height,
                                    codec->pix_fmt,
                                    dst_w, dst_h, (PixelFormat)dst_fmt,
                                    SWS_BICUBIC | SWS_ACCURATE_RND, NULL, NULL, NULL);

但是加了这个参数后,运算速度就变得巨慢了。

有一个更好的解决办法,就是在解码后把图片的 yuv 格式转成 BGRA 格式之前,先考虑一下设置 RGBA 格式的图像的宽度是 16 的倍数:

PixelFormat dst_fmt = PIX_FMT_BGRA;
int dst_w = (codec->width/16)*16;
int dst_h = codec->height;

这样在你以后用 sws_scale 时就可以直接使用 dst_w 这样的16的倍数来进行操作。这样,黑边的问题就解决了,黑边得到了裁剪,并且并不会耗费大量的计算时间,不过缺点是图像的分辨率也变小了。*最佳的解决办法,是在压缩流的输出端就保证分辨率的宽度值是 16 的整数倍*。

PS:我这里碰到这个问题,有可能是因为使用的 FFMpeg 库被修改过,用 SSE4 加速了 rgb 到 yuv420 的转换操作。原版的 FFMpeg 在这些奇怪的分辨率下是否会是如此,我没有验证。

(0) Comments    Read More   
Posted on 24-10-2014
Filed Under (技术) by waterlin

最近在产品开发中,有用到 QProcess 启动一个进程,并且与之通信。产品开发完了提交给测试组测试,发现产品的内存会不断地增长,但是这个增长速度十分缓慢,大概 12 小时 20M 左右。

怎么看都不像是常规的内存泄漏!

然后是各种找原因,先是把产品中有关视频展示及渲染的部分关掉进行测试,然后逐步把一些其它部分屏蔽掉,最后关得只剩下 socket 这个环节了,可是内存还在缓慢增长。Qt 的 socket 很靠谱啊?找来找去,终于觉得是 QProcess 惹的祸了。仔细地读了读文档,看到下面这一句话:

void QProcess::closeReadChannel ( ProcessChannel channel )
Closes the read channel channel. After calling this function, QProcess will no longer receive data on the channel. Any data that has already been received is still available for reading.
Call this function to save memory, if you are not interested in the output of the process.

其实 QProcess 是会把你启动进程的标准输出重定向到你的程序里来,从而占用你程序的内存。原来我这个缓慢增长就是由于 QProcess 启动进程后的 stdout 输出没有及时清理导致的。

如果不需要 QProcess 启动进程的标准输出,只需要在使用 QProcess 启动进程前调用 closeReadChannel 这个函数关闭输出即可。如果需要 QProcess 导过来的进程输出,则只需要在有输出数据的时候,读取干净即可。

(0) Comments    Read More   
Posted on 21-08-2014
Filed Under (技术) by waterlin

最近在 PC 上需要生成一个二维码图片,准备使用 libqrencode 这个 QR 二维码生成库。Windows 下有一个它的移植版本 qrencode-win32,除了有源代码以外,还有一个 Windows 下的应用程序,可以直接用来生成 QR 二维码。

我使用的是 libqrencode 3.4.2 版本,对应于 Windows 下的版本是使用 VC8 来构建工程的,而我本地只有 VC9 的版本,我升级工程后进行编译,提示如下错误:

Error   2       error C3163: '_vsnprintf': attributes inconsistent with previous declaration    c:\program files (x86)\microsoft visual studio 9.0\vc\include\stdio.h   358

这是因为在 VC9 CRT 里已经定义了 vsnprintf 这个函数,而在 VC8 里却没有,所以在很多 VC8 或更早的代码里,会添加类似下面的定义:

#define vsnprintf _vsnprintf

对于 libqrencode 来说,也是这个问题,只需要把 config.h 里的这一句代码注释掉即可。

如果还需要在 VC8 和 VC9 里使用,则可以使用下面的方式来灵活定制:

#if     _MSC_VER < 1500
#define vsnprintf _vsnprintf
#endif
(0) Comments    Read More   
Posted on 20-08-2014
Filed Under (技术) by waterlin

Visual C++ 2008 SP1 compiler error C1859

有一些工程,在编译好后再重新编译,会提示类似如下错误(Windows7下容易出现):

Error   1       fatal error C1859: 'release\\testproject.pch' unexpected precompiled header error, simply rerunning the compiler might fix this problem

这个就是 Visual C++ 2008 SP1 compiler error C1859 的问题,解决办法就是安装一个补丁包

除了安装上述安装包以后,还可以使用如下方式来修复该问题:

  • Disable /analyze (if enabled).
  • Invoke a clean build.
  • Reboot your machine.
  • Disable PCH files.

使用 std::copy 时的 C4996 警告

Visual C++ 2008 里如果把警告当成错误处理时进行编译,下面这个警告就会被当成错误,很扎眼,而你,则一定需要解决这个问题。

Warning 2       warning C4996: 'std::copy': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'

这是在使用 Visual C++ 自带的标准库时,Visual C++ 的编译器会加强检查,标准库里的一些方法会认为在使用上是不安全的。Visual C++ 对此提出警告、不建议使用这些函数,并且 Windows 提供了一些其它以 _s 结尾的函数用来替代这些方法。std::copy 就是这个现象的重灾区。

如果是你自己的代码碰到这个问题,那很好解决,你只需要把这些函数替换成 Windows 提供的更安全的函数即可。但是,如果是你引用的第三方库的代码问题,而你又在 Visual Studio 里把安全级别设置成不允许有警告,这个时候,就只能通过定义一个 _SCL_SECURE_NO_WARNINGS 宏来解决此问题。

(0) Comments    Read More   
Posted on 08-02-2014
Filed Under (技术) by waterlin

平时都是使用 log4cxx 动态链接库,如果需要使用 log4cxx 静态链接库的方式,那还着实需要花一番力气。

  1. 为预编译器定义 LOG4CXX_STATIC 宏。
  2. 静态链接 log4cxx 库,除了正常使用填入如下链接库外:
    log4cxxd.lib
    aprd.lib
    aprutild.lib
    xmld.lib
    

    还需要添加

    Ws2_32.lib
    Mswsock.lib
    

    这两个库,否则会有如下出错提示:

    错误        1       error LNK2019: 无法解析的外部符号 _TransmitFile@28,该符号在函数 _apr_socket_sendfile@24 中被引用       e:\water\CookVideo2Code\30Kernel\VideoClipper\LogWater\aprd.lib       1
    
  3. 在 Visual Studio 2008 工程里的 object/library modules:下方填入:Mswsock.lib
  4. 如果显示如下错误:
    错误        36      error LNK2019: 无法解析的外部符号 __imp__WSARecv@28,该符号在函数 _apr_socket_recv@12 中被引用  e:\water\CookVideo2Code\30Kernel\VideoClipper\LogWater\aprd.lib       1
    

    则添加如下库:aprd.lib。

这样,基本上就可以在你的工程里静态链接 log4cxx 库了。

(0) Comments    Read More   
Posted on 16-08-2013
Filed Under (技术) by waterlin

这两天在写 Qt 代码时,用 QPixmap 的 load 或是 loadFromData 方法来从图片文件里导入 Jpeg 图像数据,结果在 debug 版本下可以正确导入图像,可是在 release 版本下却没有办法导入图像。

这个时候,原因多半是 Qt 不支持这个图像格式,可以用下面的代码来检查当前的 Qt 版本支持的图像格式种类,并检查里面是否支持 JPEG 图像格式。

QString fileFormats = "";
/* Get all inputformats */
for (int i = 0; i < QImageReader::supportedImageFormats().count(); i++) {
        fileFormats += "*."; /* Insert wildcard */
        fileFormats
                        += QString(QImageReader::supportedImageFormats().at(i)).toLower(); /* Insert the format */
        fileFormats += " "; /* Insert a space */
}

从结果可以看出,当前的 Release 版本的确是不支持 Jpeg 图像格式。是由什么原因造成的这个现象呢?

这里情况比较特殊:并不是 Qt 不支持这个图像格式,而是需要在 release 版本里包括这个解码库。即把 qjpeg4.dll 链接库拷贝成 release 目录下的

imageformats/qjpeg4.dll

文件。你只需要让 Release 版本的可执行文件,可以正确找到该编码库链接库即可。请注意当你打包成安装文件进行安装时,一定要注意把该链接库安装到目录 imageformats 下,否则依然无法正确使用。

类似的问题,同样会存在于字符集编码等其它插件里:当你使用了指定的字符集编码时,则同样需要打包类似的库。例如,对于简体中文来说,需要把 qcncodecs4.dll 打包安装成

/codecs/qcncodecs4.dll

文件。

(0) Comments    Read More   
Posted on 23-01-2013
Filed Under (技术) by waterlin

通常情况下使用 log4cxx 都是通过一个 log4cxx 配置文件来配置相关参数的,通常描述 log4cxx 的文档1, 2,都会对此作详细的介绍。

但是,如果我能直接在 C/C++ 代码里设置 log4cxx 的属性,这样就可以不使用属性配置文件了。在某些情况下,这样还是更方便一些。从 log4xxx Wiki 获取的示例代码就是起这个作用的,代码如下:

#include <log4cxx/logger.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/simplelayout.h>

int main() {
    log4cxx::FileAppender * fileAppender = new log4cxx::FileAppender( log4cxx::LayoutPtr(new log4cxx::SimpleLayout()),
                                                                      "logfile", false);

    log4cxx::helpers::Pool p;
    fileAppender->activateOptions(p);

    log4cxx::BasicConfigurator::configure(log4cxx::AppenderPtr(fileAppender));
    log4cxx::Logger::getRootLogger()->setLevel(log4cxx::Level::getDebug());
    log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("logger");

    LOG4CXX_INFO(logger,"Created FileAppender appender");

    return 0;
}

上面的代码仅仅是把日志输出到日志文件,如果我们想同时把日志重定向到终端和文件,我们应该添加一个 log4cxx::ConsoleAppender 类来支持 log4cxx::BasicConfigurator 的初始化。下面是示例代码:

#include <log4cxx/logger.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/simplelayout.h>
#include "log4cxx/consoleappender.h"

LoggerPtr logger;

int main() {
    log4cxx::FileAppender * fileAppender = new log4cxx::FileAppender(log4cxx::LayoutPtr(new log4cxx::SimpleLayout()), L"logfile", false);

    log4cxx::ConsoleAppender * consoleAppender = new log4cxx::ConsoleAppender(log4cxx::LayoutPtr(new log4cxx::SimpleLayout()));

    log4cxx::helpers::Pool p;
    fileAppender->activateOptions(p);

    log4cxx::BasicConfigurator::configure(log4cxx::AppenderPtr(fileAppender));
    log4cxx::BasicConfigurator::configure(log4cxx::AppenderPtr(consoleAppender));
    log4cxx::Logger::getRootLogger()->setLevel(log4cxx::Level::getDebug());
    log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("logger");

    LOG4CXX_INFO(logger,"Created FileAppender appender");

    return 0;
}

通过上面的代码,在没有 log4cxx 配置文件的情况下,日志也同样可以同时输出到终端和日志文件了。

(0) Comments    Read More   
Posted on 21-12-2012
Filed Under (技术) by waterlin

我在使用 Qt 的 QTcpSocket 类来创建一个网络通信应用时,编译器提示说以下错误:

main.cpp:3:22: fatal error: QTcpSocket: No such file or directory

这个错误指向了代码

#include <QTcpSocket>

。那究竟这个 QTcpSocket 类在哪里,我需要怎样链接它呢?

通过万能的 google 找到了答案,我们只需要把下面的指令

QT += network

添加到 .pro 工程文件里,这个 .pro 文件是由

qmake -project

来生成的。如果不显式地把 QTcpSocket 添加到 .pro 文件里,该模块将不会自动加载。

这里有一点需要强调一下:在你编辑了 .pro 文件后使用命令 qmake -project,依然会获得上述错误,因为使用命令 qmake -project 将会根据当前目录下的 C++ 文件重新生成 .pro 文件。所以,在日常命令使用中,我们需要搞清楚每一个命令的具体作用。

上述命令我也在之前的文章里有介绍过,完整的 Qt 工程生成命令如下:

qmake -project
qmake -tp vc

现在,上述编译错误已经解决了,千万要记住 qmake -project 是用来生成 .pro 文件的,而 qmake -tp vc 则是用来生成 Visual Studio 工程文件的。

(0) Comments    Read More