懒人李冰

记录我的生活、学习

编程小技巧

本篇博客主要记录在写代码过程中遇到的一些小技巧,它并不是特别难以实现的复杂算法,也不是对某种特定语言的记录,而是在 工作中遇到某个问题时,自然而然能想到的解决方法,通常是一些比较通用的小技巧。

获取文件路径后缀名

工作中经常遇到对一批视频文件进行统一处理的情况,有时会根据文件名的不同后缀名进行不同的处理操作。此时就需要首先获取文件的后缀名, 之后再根据后缀名的不同进行相应的操作。

C 语言实现

实现思路:获取文件字符串的最后一个字符,依次向前寻找..后面即为后缀名。

get_filename_extension
1
2
3
4
5
6
7
8
static inline char *get_filename_extension( char *filename )
{
    char *ext = filename + strlen( filename );
    while( *ext != '.' && ext > filename )
        ext--;
    ext += *ext == '.';
    return ext;
}

C++ 语言实现

实现思路:首先将文件或路径名转换为一个string类,使用它的成员函数rfind找到最后一个.的位置,最后使用substr成员函数返回.后的所有内容,即得后缀名。

get_filename_extension
1
2
3
4
5
6
7
8
9
10
using std::string;
string getFileExt(const string& s)
{
    size_t i = s.rfind('.', s.length());
    if(i != string::npos)
    {
        return(s.substr(i+1, s.length() - i));
    }
    return ("");
}

Shell 脚本实现

get_filename_extension
1
2
3
4
5
6
7
8
9
10
#!/bin/sh
fullfilename=$1
filename=$(basename "$fullfilename")
fname="${filename%.*}"
ext="${filename##*.}"

echo "Input File:$fullfilename"
echo "Filename without Path:$filename"
echo "Filename without Extension:$fname"
echo "File Extension without Name:$ext"

调试信息分级打印

在工作中,经常遇到需要将调试信息分级打印的情况。比如在码流播放中可能默认要打印出码流的宽高、码流的 CODEC 类型等基本信息,可以定义此类信息级别为LOG_INFO级别; 码流播放时,可能会出现错误,此类信息级别为LOG_ERROR等。

实现思路:将需要打印的信息级别与默认打印信息级别进行比较,级别高时,将信息打印出来;级别低时,不打印信息。

log_level
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define LOG_DEFAULT 2
#define LOG_NONE    (-1)
#define LOG_ERROR   0
#define LOG_WARNING 1
#define LOG_INFO    2
#define LOG_DEBUG   3

void Printf(int i_level, const char *psz_fmt, va_list arg)
{
   char *psz_prefix;
   switch(i_level)
   {
        case LOG_ERROR:
            psz_prefix = "error";
            break;
        case LOG_WARNING:
            psz_prefix = "warning";
            break;
        case LOG_INFO:
            psz_prefix = "info";
            break;
        case LOG_DEBUG:
            psz_prefix = "debug";
            brengak;
        default:
            psz_prefix = "unknown";
            break;
   }
   vfprintf(stderr, psz_fmt, arg);
}

void LOG_PRINT(int i_level, const char *psz_fmt, ...);
void LOG_PRINT(int i_level, const char *psz_fmt, ...)
{
    if(i_level <= LOG_DEFAULT)
    {
        va_list arg;
        va_start(arg, psz_fmt);
        Printf(i_level, psz_fmt, arg);
        va_end(arg);
    }
}

分析特定格式的文件

工作中在验证芯片的vdec模块是否正常工作时,需要大量的跑一些码流,这些码流通常会放到一个filelist中,因为需要测试的项不同,此时就可以 通过按照一定的格式并列存放这些码流,例如根据不同的codec、测试比较YUVCRC,是要连续测试,还是要中途停止方便Debug问题,我们可以按照如下格式对filelist进行定义:

1
Codec_Type  Compare_Type Test_Type Bitstream_Full_Path

对于上面这种filelist,可以通过fscanf来逐个的获取特定的字符串,并通过feof来判断文件文件是否读取完毕, 之后使用strcmp来与特定的字符串进行匹配。例如,有如下的一个filelist.txt

1
2
HEVC Compare_CRC Debug F:\FFmpeg\hevc_bitstream1.bin
H264 Compare_YUV Release F:\FFmpeg\h264_bitstream2.bin

分析filelist.txt示例代码:

parse_filelist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char Codec_Type[10];
char Compare_Type[10];
char Release_Type[10];
char Bitstream_Path[200];

FILE *pFile = fopen("./filelist.txt", "rb");
if(pFile == NULL)
{
    fprintf(stderr, "open file fail %s", strerror(errno));
}

while(!feof(pFile))
{
    fscanf(pFile, "%s %s %s %s", Codec_Type, Compare_Type, Release_Type, Bitstream_Path);
}

if(!strcmp("HEVC", Codec_Type)) printf("Codec Type is HEVC\n");
if(!strcmp("H264", Codec_Type)) printf("Codec Type is H264\n");
...

if(!strcasecmp("hevc", Codec_Type)) printf("Codec Type is HEVC\n");
...

注意上面表示出来了通过strcmp来判断Codec_Type的类型,后面的Compare_Type可以用同样的方法来给出。

在使用过程中,人们并不会特别在意字母的大小写,但要表达的意思通常是一样的,比如HEVChevcHevc通过都是一样的,如果此时还用strcmp来判断,会出错,为此,我们提出了strcasecmp的使用方法,来避免大小写带来的问题,这也算是编写类似代码的一个小技巧。

fopen 函数个数限制

严格来讲,这个并不是编程的一些小的技巧,而是自己在工作中遇到的一个小问题,最近在每晚上跑测试时,经常遇到一晚上跑完 503 个测试后,程序就会崩溃掉,给出的提示信息是”Open File Fail”,起初是通过观察errno的类型来Debug出错的原因,最后定位到问题是, 对每个文件都打开了两次,而关闭只有一次,导致文件描述符的个数爆掉了。这个问题的原因是在不同的系统中,都会有对文件描述符的最大个数有一定的限制。

在<UNIX环境高级编程:文件I/O>中有这样的解释:

当读或写一个文件时,使用open返回的文件描述符标识该文件,将其作为参数传送给readwrite。文件描述符的变化范围是0~OPEN_MAX

关于文件描述符的最大个数问题,从stackoverflow上找到了以下几个问题的回复,可参考:

1.Is there a limit on number of open files in Windows
2.maximum-number-of-files-that-can-be-opened-by-c-fopen-in-linux
3.fopen-problem-too-many-open-files

关于fopen的使用,通常会判断返回值是否NULL来判断是否打开成功,其实除此之外,还可以继续监测出错的类型errno,并用strerror()函数直接显示出错的具体原因。技巧如下:

fopen_tips
1
2
3
4
5
6
7
#include <error.h>

FILE *pFile = fopen("file_full_path", "rb");
if(pFile == NULL)
{
    fprintf(stderr, "Open File Fail:%s\n", strerror(errno));
}

函数声明

函数定义是指对函数功能的确立,包括指定函数名、函数值类型、形参类型、函数体等,它是一个完整的、独立的函数单位。

函数声明的作用则是把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用函数时系统按此进行对照检查。

注意,C 语言环境下,如果函数不进行声明就使用,可能会产生错误,因为默认将返回值作为 int 类型来处理,所以,最好是在使用之前对函数进行声明。

这个错误之前搞了我好久……囧,例子如下。

float_int.c
1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

float mid(float a, float b)
{
    return (a + b) * 0.5;
}
main.c
1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("%f\n", mid(2, 5));
    return 0;
}

编译上面的两个函数gcc main.c float_int.c, 之后运行它,输出时 0.00000.

错误的原因是没有函数声明,默认认为函数返回值类型为 int.