原文地址: 交叉编译多平台 FFmpeg 库并提取视频帧

交叉编译多平台 FFmpeg 库并提取视频帧

本文档适用于 x86 平台编译 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 平台的 ffmpeg 运行库

编译新版 FFmpeg 请将 NDK 版本替换为 R17c 以上,将编译脚本中 extra-cflags 参数的值修改为 "$CFALGS -Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated -isysroot $NDK_PATH/sysroot -I$NDK_PATH/sysroot/usr/include/$4",并在 ./configure 命令结尾添加 --enable-avresample --enable-nonfree --enable-postproc

开发环境

编译环境: Ubuntu 1810 x64

开发环境: Windows 10

IDE: Android Studio 3.4.1

Android: 7.1

FFmpeg: 3.4.6

编译流程

下载 FFmpeg 源码: Download FFmpeg

解压后进入源码包,创建 build.sh 文件,并赋予执行权限

tar zxvf ffmpeg-3.4.6.tar.gz
cd ffmpeg-3.4.6
touch build.sh
chmod +x build.sh

将以下脚本写入 build.sh

NDK_PATH 建议下载 Revision 15C 版本

根据实际情况修改 NDK_PATH,TOOLCHAIN_VERSION 及 ANDROID_VERSION

#!/bin/sh

MY_LIBS_NAME=ffmpeg-3.4.6

# 编译产生的中间件目录
MY_BUILD_DIR=binary # NDK 目录
NDK_PATH=/usr/android-sdk-linux/android-ndk-r15c
# 编译平台
BUILD_PLATFORM=linux-x86_64
# NDK 中交叉编译工具版本
TOOLCHAIN_VERSION=4.9
# Android API Level
ANDROID_VERSION=26 ANDROID_ARMV5_CFLAGS="-march=armv5te"
ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"
ANDROID_ARMV8_CFLAGS="-march=armv8-a"
ANDROID_X86_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel" # params($1: arch, $2: arch_abi, $3: host, $4: cross_prefix, $5: cflags)
build_bin() { echo "------------------- Start build $2 -------------------------" ARCH=$1 # arm arm64 x86 x86_64
ANDROID_ARCH_ABI=$2 # armeabi armeabi-v7a x86 mips PREFIX=$(pwd)/dist/${MY_LIBS_NAME}/${ANDROID_ARCH_ABI}/ HOST=$3
SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ARCH} CFALGS=$5 TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}
CROSS_PREFIX=${TOOLCHAIN}/bin/$4- # build 中间件
mkdir -p ${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}
BUILD_DIR=./${MY_BUILD_DIR}/${ANDROID_ARCH_ABI} echo "pwd==$(pwd)"
echo "ARCH==${ARCH}"
echo "PREFIX==${PREFIX}"
echo "HOST==${HOST}"
echo "SYSROOT=${SYSROOT}"
echo "CFALGS=$5"
echo "CFALGS=${CFALGS}"
echo "TOOLCHAIN==${TOOLCHAIN}"
echo "CROSS_PREFIX=${CROSS_PREFIX}" mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR} sh ../../configure \
--prefix=${PREFIX} \
--target-os=linux \
--arch=${ARCH} \
--sysroot=$SYSROOT \
--enable-cross-compile \
--cross-prefix=${CROSS_PREFIX} \
--extra-cflags="$CFALGS -Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated" \
--extra-cxxflags="-D__thumb__ -fexceptions -frtti" \
--extra-ldflags="-L${SYSROOT}/usr/lib" \
--enable-shared \
--enable-asm \
--enable-neon \
--disable-encoders \
--enable-encoder=aac \
--enable-encoder=mjpeg \
--enable-encoder=png \
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=png \
--disable-demuxers \
--enable-demuxer=image2 \
--enable-demuxer=h264 \
--enable-demuxer=aac \
--disable-parsers \
--enable-parser=aac \
--enable-parser=ac3 \
--enable-parser=h264 \
--enable-gpl \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--disable-debug \
--enable-small make clean
make
make install cd ../../ echo "------------------- $2 Build finish -------------------------"
} # build for armeabi
#build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS" # build for armeabi-v7a
#build_bin arm armeabi-v7a arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV7_CFLAGS" # build for arm64-v8a
build_bin arm64 arm64-v8a aarch64-linux-android aarch64-linux-android "$ANDROID_ARMV8_CFLAGS" # build for x86
#build_bin x86 x86 x86 i686-linux-android "$ANDROID_X86_CFLAGS" # build for x86_64
#build_bin x86_64 x86_64 x86_64 x86_64-linux-android "$ANDROID_X86_64_CFLAGS"

根据需要选择脚本最后的编译命令,直接运行脚本即可自动编译

注意: 由于 JNI 只接受 .so 结尾的库文件,而 FFmpeg 的 configure 中指定了库名以版本号结尾,所以需要修改 configure 中的配置

下面的配置在 FFmpeg-3.4.6 版本中位于第 3416 行

SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'

修改为下面的格式

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

注意: 有些版本的 FFmpeg 编译时会出现下列报错

libavcodec / aaccoder.c:函数’search_for_ms’:
libavcodec / aaccoder.c:803:25:错误:预期的标识符或’(‘数字常量之前
libavcodec / hevc_mvs.c:函数’derive_spatial_merge_candidates’
libavcodec / hevc_mvs.c:368:23:错误:'y0000000’未声明(首次在此函数中使用

修改 path_to_ffmpeg_src/libavcodec/目录下的 aaccooder.c hevc_mvs.c 两个文件

将文件中 B0变量修改为其他字符,例如 BB

编译成功后目录结构

ffmpeg-3.4.6
├─ binary # 编译产生的中间件
├─ build.sh # 编译脚本
├─ Changelog
├─ compat
├─ configure
├─ CONTRIBUTING.md
├─ COPYING.GPLv2
├─ COPYING.GPLv3
├─ COPYING.LGPLv2.1
├─ COPYING.LGPLv3
├─ CREDITS
├─ dist # 编译输出的库和头文件目录
│ └─ ffmpeg-3.4.6 # 该文件夹名由 MY_LIBS_NAME 指定
│ └─ arm64-v8a # 与编译的目标平台 ABI 名称相同
│ ├─ bin
│ ├─ include # 头文件目录
│ │ ├─ libavcodec
│ │ ├─ libavdevice
│ │ ├─ libavfilter
│ │ ├─ libavformat
│ │ ├─ libavutil
│ │ ├─ libpostproc
│ │ ├─ libswresample
│ │ └─ libswscale
│ ├─ lib # 库目录,包含动态库和静态库
│ │ ├─ libavcodec-57.so
│ │ ├─ libavcodec.a
│ │ ├─ libavcodec.so -> libavcodec-57.so
│ │ ├─ libavdevice-57.so
│ │ ├─ libavdevice.a
│ │ ├─ libavdevice.so -> libavdevice-57.so
│ │ ├─ libavfilter-6.so
│ │ ├─ libavfilter.a
│ │ ├─ libavfilter.so -> libavfilter-6.so
│ │ ├─ libavformat-57.so
│ │ ├─ libavformat.a
│ │ ├─ libavformat.so -> libavformat-57.so
│ │ ├─ libavutil-55.so
│ │ ├─ libavutil.a
│ │ ├─ libavutil.so -> libavutil-55.so
│ │ ├─ libpostproc-54.so
│ │ ├─ libpostproc.a
│ │ ├─ libpostproc.so -> libpostproc-54.so
│ │ ├─ libswresample-2.so
│ │ ├─ libswresample.a
│ │ ├─ libswresample.so -> libswresample-2.so
│ │ ├─ libswscale-4.so
│ │ ├─ libswscale.a
│ │ ├─ libswscale.so -> libswscale-4.so
│ │ └─ pkgconfig
│ └─ share
├─ doc
├─ ffbuild
├─ fftools
├─ INSTALL.md
├─ libavcodec
├─ libavdevice
├─ libavfilter
├─ libavformat
├─ libavresample
├─ libavutil
├─ libpostproc
├─ libswresample
├─ libswscale
├─ LICENSE.md
├─ MAINTAINERS
├─ Makefile
├─ presets
├─ README.md
├─ RELEASE
├─ RELEASE_NOTES
├─ tests
├─ tools
└─ VERSION

将运行库导入到项目中

目录结构

ffmpegtest
├─ app
│ ├─ build
│ ├─ libs
│ └─ src
│ ├─ androidTest
│ ├─ main
│ │ ├─ java
│ │ │ └─ com
│ │ │ └─ example
│ │ │ └─ ffmpegtest
│ │ │ MainActivity.java
│ │ ├─ jni # C/C++ 源码目录
│ │ │ └─ include # 需要导入的头文件
│ │ │ ├─ libavcodec
│ │ │ ├─ libavdevice
│ │ │ ├─ libavfilter
│ │ │ ├─ libavformat
│ │ │ ├─ libavutil
│ │ │ ├─ libpostproc
│ │ │ ├─ libswresample
│ │ │ └─ libswscale
│ │ ├─ jniLibs # JNI 需要调用的运行库
│ │ │ └─ arm64-v8a # 对应 ABI 版本建立文件夹
│ │ │ ├─ libavcodec-57.so
│ │ │ ├─ libavdevice-57.so
│ │ │ ├─ libavfilter-6.so
│ │ │ ├─ libavformat-57.so
│ │ │ ├─ libavutil-55.so
│ │ │ ├─ libpostproc-54.so
│ │ │ ├─ libswresample-2.so
│ │ │ ├─ libswscale-4.so
│ │ │ └─ libswscale-4.so
│ │ └─ res
│ └─ test
└─ gradle

CMakeLists.txt 添加以下配置

include_directories(${PROJECT_SOURCE_DIR}/src/main/jni/include)

add_library(ffmpegTest
SHARED
src/main/jni/ffmpegTest.cpp ) add_library(avcodec-57 SHARED IMPORTED)
set_target_properties(avcodec-57
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so ) add_library(avfilter-6 SHARED IMPORTED)
set_target_properties(avfilter-6
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavfilter-6.so ) add_library(avformat-57 SHARED IMPORTED)
set_target_properties(avformat-57
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavformat-57.so ) add_library(avutil-55 SHARED IMPORTED)
set_target_properties(avutil-55
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavutil-55.so ) add_library(postproc-54 SHARED IMPORTED)
set_target_properties(postproc-54
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libpostproc-54.so ) add_library(avdevice-57 SHARED IMPORTED)
set_target_properties(avdevice-57
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavdevice-57.so ) add_library(swscale-4 SHARED IMPORTED)
set_target_properties(swscale-4
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswscale-4.so ) add_library(swresample-2 SHARED IMPORTED)
set_target_properties(swresample-2
PROPERTIES
IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswresample-2.so ) target_link_libraries(ffmpegTest
${log-lib}
avcodec-57 avfilter-6 avformat-57 avutil-55 postproc-54 avdevice-57 swscale-4 swresample-2)

提取视频帧并保存为图片

#include <jni.h>
#include <android/log.h> extern "C" {
#include <libavformat/avformat.h>
} #define DEBUG #ifdef DEBUG
#define LOG "ffmpegLOG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG, __VA_ARGS__)
#else
#define LOG
#define LOGD(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#define LOGF(...)
#endif int writeJPEG(AVFrame* frame, int width, int height, char* output_ath, int image_index); extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ffmpegtest_MainActivity_videoFrame(JNIEnv *env, jobject instance,
jstring filePath_, jstring outputPath_) {
const char *filePath = env->GetStringUTFChars(filePath_, 0);
const char *outputPath = env->GetStringUTFChars(outputPath_, 0); LOGE("======================= ffmpeg start ======================="); clock_t time_start, time_finish;
double total_time;
time_start = clock(); // 注册所有模块
av_register_all(); AVFormatContext *formatContext = nullptr;
int ret = 0; LOGD("Video path: [%s]", filePath);
// 打开媒体
ret = avformat_open_input(&formatContext, filePath, nullptr, nullptr);
if (ret < 0) {
LOGE("Cannot open file, error code: [%d]", ret);
return -1;
} // 获取媒体信息
ret = avformat_find_stream_info(formatContext, nullptr);
if (ret < 0) {
LOGE("Cannot find stream, error code: [%d]", ret);
return -1;
} int video_index = -1;
// 遍历媒体流
for (int i = 0; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
video_index = i;
break;
}
} if (video_index == -1) {
LOGE("Cannot find video stream");
return -1;
} // 找出一个有效码流的 AVCodecID,根据标准寻找对应的解码器
AVCodecContext *codecContext = formatContext->streams[video_index]->codec;
enum AVCodecID codecId = codecContext->codec_id;
AVCodec *codec = avcodec_find_decoder(codecId);
if(!codec){
LOGE("Cannot find decoder");
return -1;
} // 初始化解码器
ret = avcodec_open2(codecContext, codec, nullptr);
if (ret < 0) {
LOGE("Cannot open decoder, error code: [%d]", ret);
return -1;
} // 分配内存
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
int image_index = 0; // 当剩余帧数大于 0 时
while (av_read_frame(formatContext, packet) >= 0) {
if (packet && packet->stream_index == video_index) {
int gotFrame = 0;
// 将 AVPacket 中的数据解码为原始数据(YUV、RGB 以及 PCM),存储在 AVFrame 上
avcodec_decode_video2(codecContext, frame, &gotFrame, packet);
if (gotFrame) {
image_index++;
// 将视频帧保存在本地
ret = writeJPEG(frame, codecContext->width, codecContext->height, (char*)outputPath, image_index);
if(ret == 0){
LOGI("Save frame in %s and rename to video_frame_%d.jpg", outputPath, image_index);
}
}
}
}
time_finish = clock();
total_time = (double)(time_finish - time_start) / CLOCKS_PER_SEC;
LOGE("Total time: [%f]s --- ffmpeg", total_time);
av_packet_unref(packet);
if (frame) {
av_frame_free(&frame);
}
avcodec_close(codecContext);
avformat_free_context(formatContext);
LOGE("======================= ffmpeg finish ======================="); env->ReleaseStringUTFChars(filePath_, filePath);
env->ReleaseStringUTFChars(outputPath_, outputPath); return 0;
} int writeJPEG(AVFrame *frame, int width, int height, char *output_path, int image_index) {
char out_file[1024];
sprintf(out_file, "%s/video_frame_%d.jpg", output_path, image_index);
// 分配内存空间
AVFormatContext *formatContext = avformat_alloc_context();
// 初始化 AVFormatContext 结构体
avformat_alloc_output_context2(&formatContext, nullptr, "singlejpeg", out_file); // 打开(创建?)要写入的文件
if (avio_open2(&formatContext->pb, out_file, AVIO_FLAG_READ_WRITE, nullptr, nullptr) < 0) {
LOGE("Open file failed---write JPEG");
return -1;
} // 创建流通道,例如 Video - H.264, Audio - AAC
AVStream *stream = avformat_new_stream(formatContext, nullptr);
if (&stream == nullptr) {
LOGE("Create stream failed---write JPEG");
return -1;
} AVCodecContext *codecContext = stream->codec; // 保存文件头信息(帧信息)
codecContext->codec_id = formatContext->oformat->video_codec;
codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
codecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
codecContext->height = height;
codecContext->width = width;
codecContext->time_base.num = 1;
codecContext->time_base.den = 25; // 寻找解码器
AVCodec *codec = avcodec_find_encoder(codecContext->codec_id);
if (!codec) {
LOGE("Cannot find encoder---write JPEG");
return -1;
} // 初始化解码器
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
LOGE("Cannot open encoder---write JPEG");
return -1;
}
// 将文件头保存到 codecpar 中
avcodec_parameters_from_context(stream->codecpar, codecContext); // 写入头数据
avformat_write_header(formatContext, nullptr);
int size = codecContext->width * codecContext->height; // 创建并初始化 AVPacket 内存空间
AVPacket *packet = av_packet_alloc();
av_new_packet(packet, size * 3); int got_image = 0;
// 调用编码器,编码为指定格式
int result = avcodec_encode_video2(codecContext, packet, frame, &got_image);
if (result < 0) {
LOGE("Encode failed---write JPEG");
return -1;
}
if (got_image == 1) {
// 输出一帧数据
av_write_frame(formatContext, packet);
}
// 释放包内存
av_packet_unref(packet);
// 写文件尾
av_write_trailer(formatContext); // 关闭文件
avio_close(formatContext->pb);
// 释放解码器
avcodec_close(codecContext);
avformat_free_context(formatContext); return 0;
}

在 Activity 中调用

public class MainActivity extends AppCompatActivity {
// 导入运行库
static {
System.loadLibrary("ffmpegTest");
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取存储设备路径
String storagePath = Environment.getExternalStorageDirectory().getPath();
File videoPath = new File(storagePath + "/Download/testVideo.mp4");
videoFrame(videoPath.toString(), storagePath + "/Download/video_frames/");
}
} // 实例化运行库中的方法
public native int videoFrame(String filePath, String outputPath);

交叉编译多平台 FFmpeg 库并提取视频帧的更多相关文章

  1. 交叉编译多平台 FFmpeg 库并提取视频帧(转)

    交叉编译多平台 FFmpeg 库并提取视频帧 转  https://www.cnblogs.com/leviatan/p/11142579.html 本文档适用于 x86 平台编译 armeabi.a ...

  2. C++调用ffmpeg.exe提取视频帧

    有时候,我们获得一段视频,需要将其中的每一帧都提取出来,来进行一些相关的处理,这时候我们就可以需要用到ffmpeg.exe来进行视频帧的提取. ffmpeg简介:FFmpeg是一套可以用来记录.转换数 ...

  3. 基于C#利用ffmpeg提取视频帧

    利用ffmepg提取视频帧实际上是利用C#调用ffmepg命令行进行处理对应的视频,然后输出出视频帧 GetPicFromVideo("); static public string Get ...

  4. Windows下 ffmpeg + labelImg 提取视频帧 得到图片集 并 标注图片 来 构造数据集

    构造数据集的流程 视频文件  >>  ffmpeg处理  >>  图片集  >>  labelImg进行标注  >>  标注好的数据集 准备ffmpeg ...

  5. ffmpeg-python 任意提取视频帧

    ▶ 环境准备 1.安装 FFmpeg 音/视频工具 FFmpeg 简易安装文档 2.安装 ffmpeg-python pip3 install ffmpeg-python 3.[可选]安装 openc ...

  6. FFmpeg进行视频帧提取&音频重采样-Process.waitFor()引发的阻塞超时

    由于产品需要对视频做一系列的解析操作,利用FFmpeg命令来完成视频的音频提取.第一帧提取作为封面图片.音频重采样.字幕压缩等功能: 前一篇文章已经记录了FFmpeg在JAVA中的使用-音频提取&am ...

  7. 实战FFmpeg--编译iOS平台使用的FFmpeg库(支持arm64的FFmpeg2.6.2)

    编译环境:Mac OS X 10.10.2 ,Xcode 6.3  iOS SDK 8.3        FFmpeg库的下载地址是 http://www.ffmpeg.org/releases/ . ...

  8. FFmpeg编译:mac下编译iOS平台的FFmpeg库(支持armv7, arm64, i386, x86_64)

    环境:FFmpeg 3.4.6Xcode 10.3macOS 10.14.6iOS SDK 12.4 一.准备工作 1. 下载FFmpeg我这里使用的是3.4.6版本的FFmpeg,可以从FFmpeg ...

  9. libavcodec是一款LGPL自由软件编解码库,用于视频和音频数据的编解码工作

    http://zh.wikipedia.org/zh-cn/Libavcodec http://baike.baidu.com/view/856526.htm libavcodec是一款LGPL自由软 ...

随机推荐

  1. WPF 实现拖动工具箱效果

    原文:WPF 实现拖动工具箱效果 1.效果 点击左边的矩形拖动到右边canvas面板,右边面板添加矩形 2.布局 左边是个StockPanel,上面有个矩形,右边是个Canvas面板. 矩形是源,Ca ...

  2. [nginx]invalid number of arguments

    invalid number of arguments nginx出现以下的错误,基本上错误的原因就是少了后面的分号导致. invalid number of arguments

  3. IIS基本介绍

    应用程序池-网站-应用程序   1 应用程序池 设置应用程序的各种设置,新建.修改应用程序的时候可以选择应用程序池   2 [站外图片上传中...(image-3924c8-1511163001873 ...

  4. RelativeSource 简述

    原文:RelativeSource 简述 RelativeSource实现标记扩展,以描述绑定源相对于绑定目标的位置. <Binding> <Binding.RelativeSour ...

  5. makedownpad安装解锁

    http://blog.csdn.net/na_beginning/article/details/53414102

  6. wpf屏蔽快捷键alt+space,alt+F4

    /// <summary>        /// 阻止 alt+f4和alt+space 按键        /// </summary>        /// <par ...

  7. VS2012 调试Web项目 遭遇 HTTP 错误 500.23 - Internal Server Error

    原文:VS2012 调试Web项目 遭遇 HTTP 错误 500.23 - Internal Server Error 在使用vs2012 调试Web站点时 报错 500.23,详细如图 此错误是因为 ...

  8. UWP入门(十)--获取文件属性

    原文:UWP入门(十)--获取文件属性 重要的 API StorageFile.GetBasicPropertiesAsync StorageFile.Properties StorageItemCo ...

  9. C#图片处理之:旋转图片90度的整数倍

    原文:C#图片处理之:旋转图片90度的整数倍 旋转图片90的整数倍那真是太简单了.         public static Bitmap KiRotate90(Bitmap img)        ...

  10. Redis系统管理

    EXISTS/DEL exists <key>判断某个key是否存在 del <key>删除某个key *** TYPE/KEYS type <key>获取key的 ...