一个偶然的机缘,好像要做直播相关的项目

为了筹备,前期做一些只是储备,于是开始学习ffmpeg

这是学习的第一课

做一个简单的播放器,播放视频画面帧

思路是,将视频文件解码,得到帧,然后使用定时器,1秒显示24帧

1.创建win32工程,添加菜单项 “打开”

为了避免闪烁,MyRegisterClass中设置hbrBackground为null

2.在main函数中初始化ffmpeg库:av_register_all();

3.响应菜单打开

 void LoadVideoPlay(HWND hWnd)
{
if (gbLoadVideo)
{
return;
} TCHAR szPath[] = { };
DWORD dwPath = ;
OPENFILENAME ofn = { };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.hInstance = hInst;
ofn.lpstrFile = szPath;
ofn.nMaxFile = dwPath;
ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0");
ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrInitialDir = _T("F:\\"); if (!GetOpenFileName(&ofn))
{
DWORD dwErr = CommDlgExtendedError();
OutputDebugString(_T("GetOpenFileName\n"));
return;
} std::wstring strVideo = szPath;
std::thread loadVideoThread([hWnd, strVideo]() {
gbLoadVideo = TRUE;
std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size());
OpenVideoByFFmpeg(hWnd, sVideo.c_str());
gbLoadVideo = FALSE;
}); loadVideoThread.detach();
}

使用c++11的线程来加载视频文件并进行解码工作。

4.在加载完视频之后,设置窗口为不可缩放

创建缓存DC等显示环境

设置播放帧画面的定时器

5.将解码的帧画面转化为 RGB 32位格式,并存储至队列中等待播放

6.播放帧画面

在WM_PAINT消息中进行绘画

 void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight)
{
do
{
if (GetFramesSize() > ( * )) // 缓冲5秒的帧数
{
if (WaitForSingleObject(ghExitEvent, ) == WAIT_OBJECT_0)
{
return;
}
}
else
{
HDC hDC = GetDC(hWnd);
HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, , buffer);
if (hFrame)
{
PushFrame(hFrame);
}
ReleaseDC(hWnd, hDC);
break;
} } while (true);
}

因为解码拿到的是像素数据,需要将像素数据转化为Win32兼容位图

 HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits)
{
if (uBitsPerPixel <= ) // NOT IMPLEMENTED YET
return NULL; HBITMAP hBitmap = ;
if (!uWidth || !uHeight || !uBitsPerPixel)
return hBitmap;
LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / );
BITMAPINFO bmpInfo = { };
bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;
bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的
bmpInfo.bmiHeader.biWidth = uWidth;
bmpInfo.bmiHeader.biPlanes = ;
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
// Pointer to access the pixels of bitmap
UINT * pPixels = ;
hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&
bmpInfo, DIB_RGB_COLORS, (void **)&
pPixels, NULL, ); if (!hBitmap)
return hBitmap; // return if invalid bitmaps memcpy(pPixels, pBits, lBmpSize); return hBitmap;
}

7.播放完毕,回复窗口设定,关闭定时器

代码流程一目了然,用来学习ffmpeg入门

最后贴上总的代码

 // main.cpp : 定义应用程序的入口点。
// #include "stdafx.h"
#include "testPlayVideo.h" #include <windows.h>
#include <commdlg.h>
#include <deque>
#include <string>
#include <mutex>
#include <thread> extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libavutil/imgutils.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
} #pragma comment(lib, "Comdlg32.lib") #define MAX_LOADSTRING 100
#define TIMER_FRAME 101
#define CHECK_TRUE(v) {if(!v) goto cleanup;}
#define CHECK_ZERO(v) {if(v<0) goto cleanup;} // 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名 std::mutex gFrameLock; // 图像位图帧的访问锁
std::deque<HBITMAP> gFrames; // 所有解码的视频图像位图帧
HDC ghFrameDC; // 视频帧图像兼容DC
HBITMAP ghFrameBmp; // 兼容DC的内存位图
HBRUSH ghFrameBrush; // 兼容DC的背景画刷
HANDLE ghExitEvent; // 程序退出通知事件
UINT uFrameTimer; // 定时器,刷新窗口显示图像位图帧
UINT uBorderWidth;
UINT uBorderHeight;
BOOL gbLoadVideo;
DWORD dwWndStyle; // 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits); HBITMAP PopFrame(void)
{
std::lock_guard<std::mutex> lg(gFrameLock); if (gFrames.empty())
{
return nullptr;
}
else
{
HBITMAP hFrame = gFrames.front();
gFrames.pop_front();
return hFrame;
}
} void PushFrame(HBITMAP hFrame)
{
std::lock_guard<std::mutex> lg(gFrameLock);
gFrames.push_back(hFrame);
} size_t GetFramesSize(void)
{
std::lock_guard<std::mutex> lg(gFrameLock);
return gFrames.size();
} void ReleasePaint(void)
{
if (ghFrameDC) DeleteDC(ghFrameDC);
if (ghFrameBmp) DeleteObject(ghFrameBmp);
if (ghFrameBrush) DeleteObject(ghFrameBrush); ghFrameDC = nullptr;
ghFrameBmp = nullptr;
ghFrameBrush = nullptr;
} void CreatePaint(HWND hWnd)
{
RECT rc;
GetClientRect(hWnd, &rc);
HDC hDC = GetDC(hWnd);
ghFrameDC = CreateCompatibleDC(hDC);
ghFrameBmp = CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top);
ghFrameBrush = CreateSolidBrush(RGB(, , ));
SelectObject(ghFrameDC, ghFrameBmp);
ReleaseDC(hWnd, hDC);
} void ReleaseFrames(void)
{
std::lock_guard<std::mutex> lg(gFrameLock);
for (auto& hFrame : gFrames)
{
DeleteObject(hFrame);
}
gFrames.clear(); ReleasePaint();
} void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight)
{
do
{
if (GetFramesSize() > ( * )) // 缓冲5秒的帧数
{
if (WaitForSingleObject(ghExitEvent, ) == WAIT_OBJECT_0)
{
return;
}
}
else
{
HDC hDC = GetDC(hWnd);
HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, , buffer);
if (hFrame)
{
PushFrame(hFrame);
}
ReleaseDC(hWnd, hDC);
break;
} } while (true);
} std::string UnicodeToUTF_8(const wchar_t *pIn, size_t nSize)
{
if (pIn == NULL || nSize == )
{
return "";
} std::string s;
int n = WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, NULL, , NULL, NULL);
if (n > )
{
s.resize(n);
WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, &s[], n, NULL, NULL);
} return s;
} int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine); // TODO: 在此放置代码。
ghExitEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
av_register_all(); // 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_TESTPLAYVIDEO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance); // 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
} HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTPLAYVIDEO)); MSG msg; // 主消息循环:
while (GetMessage(&msg, nullptr, , ))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} return (int) msg.wParam;
} //
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = ;
wcex.cbWndExtra = ;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTPLAYVIDEO));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = /*(HBRUSH)(COLOR_WINDOW+1)*/nullptr;
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTPLAYVIDEO);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex);
} //
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中 HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, , CW_USEDEFAULT, , nullptr, nullptr, hInstance, nullptr); if (!hWnd)
{
return FALSE;
} ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd); return TRUE;
} HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits)
{
if (uBitsPerPixel <= ) // NOT IMPLEMENTED YET
return NULL; HBITMAP hBitmap = ;
if (!uWidth || !uHeight || !uBitsPerPixel)
return hBitmap;
LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / );
BITMAPINFO bmpInfo = { };
bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;
bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的
bmpInfo.bmiHeader.biWidth = uWidth;
bmpInfo.bmiHeader.biPlanes = ;
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
// Pointer to access the pixels of bitmap
UINT * pPixels = ;
hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&
bmpInfo, DIB_RGB_COLORS, (void **)&
pPixels, NULL, ); if (!hBitmap)
return hBitmap; // return if invalid bitmaps memcpy(pPixels, pBits, lBmpSize); return hBitmap;
} void PaintFrame(HWND hWnd, HDC hDC, RECT rc)
{
FillRect(ghFrameDC, &rc, ghFrameBrush);
HBITMAP hFrame = PopFrame();
if (hFrame)
{
BITMAP bmp;
GetObject(hFrame, sizeof(bmp), &bmp);
HDC hFrameDC = CreateCompatibleDC(hDC);
HBITMAP hOld = (HBITMAP)SelectObject(hFrameDC, hFrame);
BitBlt(ghFrameDC, , , bmp.bmWidth, bmp.bmHeight, hFrameDC, , , SRCCOPY);
SelectObject(hFrameDC, hOld);
DeleteObject(hFrame);
DeleteDC(hFrameDC);
} BitBlt(hDC, , , rc.right - rc.left, rc.bottom - rc.top, ghFrameDC, , , SRCCOPY);
} void OpenVideoByFFmpeg(HWND hWnd, const char* szVideo)
{
AVFormatContext* pFmtCtx = nullptr;
AVCodecContext* pCodecCtx = nullptr;
AVCodec* pCodec = nullptr;
AVFrame* pFrameSrc = nullptr;
AVFrame* pFrameRGB = nullptr;
AVPacket* pPkt = nullptr;
UCHAR* out_buffer = nullptr;
struct SwsContext * pImgCtx = nullptr;
int ret = ;
int videoStream = -;
int numBytes = ; pFmtCtx = avformat_alloc_context();
CHECK_TRUE(pFmtCtx);
ret = avformat_open_input(&pFmtCtx, szVideo, nullptr, nullptr);
CHECK_ZERO(ret);
ret = avformat_find_stream_info(pFmtCtx, nullptr);
CHECK_ZERO(ret); for (UINT i = ; i < pFmtCtx->nb_streams; ++i)
{
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
break;
}
}
CHECK_ZERO(videoStream); pCodecCtx = avcodec_alloc_context3(nullptr);
CHECK_TRUE(pCodecCtx);
ret = avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoStream]->codecpar);
CHECK_ZERO(ret);
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
CHECK_TRUE(pCodec);
ret = avcodec_open2(pCodecCtx, pCodec, nullptr);
CHECK_ZERO(ret); pFrameSrc = av_frame_alloc();
pFrameRGB = av_frame_alloc();
CHECK_TRUE(pFrameSrc);
CHECK_TRUE(pFrameRGB); pImgCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
CHECK_TRUE(pImgCtx); numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, );
out_buffer = (UCHAR*)av_malloc(numBytes);
CHECK_TRUE(out_buffer); ret = av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer,
AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, );
CHECK_ZERO(ret); pPkt = new AVPacket;
ret = av_new_packet(pPkt, pCodecCtx->width * pCodecCtx->height);
CHECK_ZERO(ret); SetWindowPos(hWnd, nullptr, , , pCodecCtx->width + uBorderWidth, pCodecCtx->height + uBorderHeight, SWP_NOMOVE);
ReleasePaint();
CreatePaint(hWnd);
dwWndStyle = GetWindowLong(hWnd, GWL_STYLE);
::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle&~WS_SIZEBOX);
if (!uFrameTimer) uFrameTimer = SetTimer(hWnd, TIMER_FRAME, , nullptr); while (true)
{
if (av_read_frame(pFmtCtx, pPkt) < )
{
break;
} if (pPkt->stream_index == videoStream)
{
ret = avcodec_send_packet(pCodecCtx, pPkt);
if (ret < ) continue;
ret = avcodec_receive_frame(pCodecCtx, pFrameSrc);
if (ret < ) continue; ret = sws_scale(pImgCtx, pFrameSrc->data, pFrameSrc->linesize,
, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
if (ret <= ) continue;
PlayFrame(hWnd, out_buffer, pCodecCtx->width, pCodecCtx->height);
} av_packet_unref(pPkt);
} if (uFrameTimer)
{
KillTimer(hWnd, uFrameTimer);
uFrameTimer = ;
} if (dwWndStyle) ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle); cleanup:
if (pFmtCtx) avformat_free_context(pFmtCtx);
if (pCodecCtx) avcodec_free_context(&pCodecCtx);
if (pFrameSrc) av_frame_free(&pFrameSrc);
if (pFrameRGB) av_frame_free(&pFrameRGB);
if (pImgCtx) sws_freeContext(pImgCtx);
if (out_buffer) av_free(out_buffer);
if (pPkt)
{
av_packet_unref(pPkt);
delete pPkt;
}
} void LoadVideoPlay(HWND hWnd)
{
if (gbLoadVideo)
{
return;
} TCHAR szPath[] = { };
DWORD dwPath = ;
OPENFILENAME ofn = { };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.hInstance = hInst;
ofn.lpstrFile = szPath;
ofn.nMaxFile = dwPath;
ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0");
ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrInitialDir = _T("F:\\"); if (!GetOpenFileName(&ofn))
{
DWORD dwErr = CommDlgExtendedError();
OutputDebugString(_T("GetOpenFileName\n"));
return;
} std::wstring strVideo = szPath;
std::thread loadVideoThread([hWnd, strVideo]() {
gbLoadVideo = TRUE;
std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size());
OpenVideoByFFmpeg(hWnd, sVideo.c_str());
gbLoadVideo = FALSE;
}); loadVideoThread.detach();
} //
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
CreatePaint(hWnd); RECT rc;
GetClientRect(hWnd, &rc);
RECT rcWnd;
GetWindowRect(hWnd, &rcWnd);
uBorderWidth = (rcWnd.right - rcWnd.left) - (rc.right - rc.left) + ;
uBorderHeight = (rcWnd.bottom - rcWnd.top) - (rc.bottom - rc.top) + ;
}
break;
case WM_TIMER:
{
if (uFrameTimer && (uFrameTimer == wParam))
{
if (IsIconic(hWnd)) // 如果最小化了,则直接移除图像帧
{
HBITMAP hFrame = PopFrame();
if (hFrame)
{
DeleteObject(hFrame);
}
}
else
{
InvalidateRect(hWnd, nullptr, FALSE);
}
}
}
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_OPEN:
LoadVideoPlay(hWnd);
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT rc;
GetClientRect(hWnd, &rc);
HDC hdc = BeginPaint(hWnd, &ps);
PaintFrame(hWnd, hdc, rc);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
SetEvent(ghExitEvent);
ReleaseFrames();
PostQuitMessage();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return ;
} // “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE; case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}

main.cpp

完结撒花

FFmpeg入门,简单播放器的更多相关文章

  1. 视频播放器控制原理:ffmpeg之ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  2. 基于ffmpeg的C++播放器1

    基于ffmpeg的C++播放器 (1) 2011年12月份的时候发了这篇博客 http://blog.csdn.net/qq316293804/article/details/7107049 ,博文最 ...

  3. 基于FFMPEG的跨平台播放器实现(二)

    基于FFMPEG的跨平台播放器实现(二) 上一节讲到了在Android平台下采用FFmpeg+surface组合打造播放器的方法,这一节讲一下Windows平台FFmpeg + D3D.Linux平台 ...

  4. 基于FFMPEG的跨平台播放器实现

    基于FFMPEG的跨平台播放器实现 一.背景介绍 FFmpeg是一款超级强大的开源多媒体编解码框架,提供了录制.转换以及流化音视频的完整解决方案,包含了libavcodec.libavformat等多 ...

  5. 基于Live555,ffmpeg的RTSP播放器直播与点播

    基于Live555,ffmpeg的RTSP播放器直播与点播 多路RTSP高清视频播放器下载地址:http://download.csdn.net/detail/u011352914/6604437多路 ...

  6. 仿迅雷播放器教程 -- 基于ffmpeg的C++播放器 (1)

    2011年12月份的时候发了这篇博客 http://blog.csdn.net/qq316293804/article/details/7107049 ,博文最后说会开源一个播放器,没想到快两年了,才 ...

  7. ffmpeg学习(三)——ffmpeg+SDL2 实现简单播放器

    本篇实现基于ffmpeg动态库用测试程序播放本地文件和RTSP视频流. 参考文章:http://blog.csdn.net/leixiaohua1020/article/details/8652605 ...

  8. 基于libvlc和wxWidgets的简单播放器代码阅读

    源代码来自 http://git.videolan.org/?p=vlc.git;a=blob_plain;f=doc/libvlc/wx_player.cpp // g++ wx_player.cp ...

  9. 简单播放器(增加sdl事件控制)

    #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscal ...

随机推荐

  1. IM 融云 之 开发基础概念

    基础概念 - 开发篇 App Key / Secret App Key / Secret 相当于您的 App 在融云的账号和密码.是融云 SDK 连接服务器所必须的标识,每一个 App 对应一套 Ap ...

  2. Leetcode 181. Employees Earning More Than Their Managers

    The Employee table holds all employees including their managers. Every employee has an Id, and there ...

  3. LINUX 无法登入系统(2017-1-16)

    很好的博文:http://blog.csdn.net/caizi001/article/details/38659189

  4. UVa 10427 - Naughty Sleepy Boys

    题目大意:从1开始往后写数字,构成一个如下的字符串 123456789101112... .求第n位的数字是多少. 找规律,按数字的位数可以构建一个类似杨辉三角的东西,求出第n位是哪个数的第几位即可. ...

  5. Twisted源码分析系列01-reactor

    转载自:http://www.jianshu.com/p/26ae331b09b0 简介 Twisted是用Python实现的事件驱动的网络框架. 如果想看教程的话,我觉得写得最好的就是Twisted ...

  6. PHP生成带有干扰线的验证码,干扰点、字符倾斜

    PHP生成验证码的类代码,本验证码类支持生成干扰点.干扰线等干扰像素,还可以使字符倾斜.在类中你可以定义验证码宽度.高度.长度.倾斜角度等参数,后附有用法: <?php class class_ ...

  7. 在新浪sae上部署WeRoBot

    花了整整一个下午,终于在新浪sae部署完成WeRoBot,现在将其中的曲折记录下来. 首先下载WeRoBot-SAE-demo,按照README.md中的要求,执行下述命令: git clone gi ...

  8. Quartz2D 之 简单使用

    1. 获取Graphics Context CGContextRef ctx = UIGraphicsGetCurrentContext(); 2. 最后的渲染接口 CGContextStrokePa ...

  9. js原生拓展网址——mozilla开发者

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript https://developer.mozilla.org/zh-CN/docs/Web ...

  10. Discuz教程:X3.1-x3.2后台admin.php防止直接恶意访问

    功能说明:admin.php是discuz默认的后台地址,正常情况下可以直接访问,为了防止某些恶意访问的情况,可以修改以下内容进行安全性能提升.适用版本:Discuz!x1-x3.2具体实施方案: a ...