本文总阅读量 本站访客数人次 本站总访问量
gongluck's blog
C/C++ Golang 音视频流媒体
字符动画播放器,不止BadApple!

有屏幕的地方就有BadApple!在B站看过好多版本,于是我也用控制台做了一个。网上很多人都是直接显示bmp图片的,但我这个版本是用libvlc实时解码和播放的,音频用libvlc播放,画面就是转换成字符后输出到控制台。除了BadApple这种黑白动画,也能播放彩色视频。具体可以看一下效果视频。

设计思路

libvlc解码转码出RGBA和播放音频,再将RGBA量化为黑('*')白(' ‘)两个颜色并输出到屏幕。

效果视频

https://www.bilibili.com/video/BV1N64y1u7BD

代码仓库

https://github.com/gongluck/Character-player/tree/console

#include <iostream>
#include <thread>
#include <atomic>

#ifdef _WIN32
#include <Windows.h>
#define ssize_t SSIZE_T
#endif
#include <vlc/vlc.h>

#define CHECKEQUALRET(ret, compare)\
if(ret == compare)\
{\
    std::cerr << "error ocurred in " << __FILE__\
              << "`s line " << __LINE__\
              << ", error " << ret;\
    goto END;\
}

#define CHECKNEQUALRET(ret, compare)\
if(ret != compare)\
{\
    std::cerr << "error ocurred in " << __FILE__\
              << "`s line " << __LINE__\
              << ", error " << ret;\
    goto END;\
}

static int WIDTH = 200;
static int HEIGHT = 50;
static char* out_buffer = nullptr;
static char* tmp_buffer = nullptr;
static char* print_buffer = nullptr;
std::atomic<bool> atomiclock;
std::atomic<bool> gotframe = false;
static void* lock(void* data, void** p_pixels)
{
    while (atomiclock)
    {
        std::this_thread::sleep_for(std::chrono::microseconds(10));
    }
    atomiclock = true;
    *p_pixels = out_buffer;
    return 0;
}
static void unlock(void* data, void* id, void* const* p_pixels)
{
    atomiclock = false;
    gotframe = true;
}
static void display(/*void* data, void* id*/)
{
    atomiclock = true;
    if (!gotframe)
    {
        atomiclock = false;
        std::this_thread::sleep_for(std::chrono::microseconds(10));
        return;
    }
    memcpy(tmp_buffer, out_buffer, HEIGHT * WIDTH * 4);
    gotframe = false;
    atomiclock = false;

    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { 0 };
    SetConsoleCursorPosition(hConsoleOutput, pos);

    RGBQUAD* rgba = reinterpret_cast<RGBQUAD*>(tmp_buffer);
    for (int i = 0; i < HEIGHT; ++i)
    {
        for (int j = 0; j < WIDTH; ++j)
        {
            auto point = rgba[WIDTH * i + j];
            auto light = (point.rgbRed + point.rgbGreen + point.rgbBlue) / 3;
            print_buffer[i * (WIDTH + 2) + j] = light > 127 ? '*' : ' ';
        }
        print_buffer[i * (WIDTH + 2) + WIDTH] = '\r';
        print_buffer[i * (WIDTH + 2) + WIDTH + 1] = '\n';
    }
    puts(print_buffer);
}

int main(int argc, char** argv)
{
    libvlc_instance_t* inst_ = nullptr;
    libvlc_media_t* media_ = nullptr;
    libvlc_media_player_t* player_ = nullptr;
    libvlc_media_list_t* list_ = nullptr;
    libvlc_media_list_player_t* plist_ = nullptr;
    int ret = 0;

    std::cout << "Usage: Character-player [media file] [out width] [out height]" << std::endl;

    if (argc >= 4)
    {
        WIDTH = atoi(argv[2]);
        HEIGHT = atoi(argv[3]);
    }

    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD size = { WIDTH + 4, HEIGHT + 5 };
    SetConsoleScreenBufferSize(hConsoleOutput, size);
    SMALL_RECT rc = { 0,0, WIDTH + 1, HEIGHT + 1 };
    SetConsoleWindowInfo(hConsoleOutput, true, &rc);

    libvlc_log_close(nullptr);

    bool exit = false;
    std::thread th([&]()
    {
        while (!exit)
        {
            while (atomiclock)
            {
                std::this_thread::sleep_for(std::chrono::microseconds(10));
            }
            display();
        }
    });

    inst_ = libvlc_new(0, nullptr);
    CHECKEQUALRET(inst_, nullptr);
    media_ = libvlc_media_new_path(inst_, argc <= 1 ? "badapple.mp4" : argv[1]);
    CHECKEQUALRET(media_, nullptr);

    out_buffer = static_cast<char*>(malloc(HEIGHT * WIDTH * 4));
    CHECKEQUALRET(out_buffer, nullptr);
    tmp_buffer = static_cast<char*>(malloc(HEIGHT * WIDTH * 4));
    CHECKEQUALRET(tmp_buffer, nullptr);
    print_buffer = static_cast<char*>(malloc(HEIGHT * (WIDTH + 2) + 1));
    CHECKEQUALRET(print_buffer, nullptr);
    print_buffer[HEIGHT * (WIDTH + 2)] = '\0';

    //player_ = libvlc_media_player_new_from_media(media_);
    player_ = libvlc_media_player_new(inst_);
    CHECKEQUALRET(player_, nullptr);

    libvlc_video_set_callbacks(player_, lock, unlock, nullptr, 0);
    libvlc_video_set_format(player_, "RGBA", WIDTH, HEIGHT, WIDTH * 4);
    //libvlc_media_player_set_hwnd(player_, GetDesktopWindow());
    
    // play loop
    list_ = libvlc_media_list_new(inst_);
    plist_ = libvlc_media_list_player_new(inst_);
    libvlc_media_list_add_media(list_, media_);
    libvlc_media_list_player_set_media_list(plist_, list_);
    libvlc_media_list_player_set_media_player(plist_, player_);
    libvlc_media_list_player_set_playback_mode(plist_, libvlc_playback_mode_loop);
    libvlc_media_list_player_play(plist_);

    std::cin.get();

END:
    exit = true;
    if (th.joinable())
    {
        th.join();
    }

    if (player_ != nullptr)
    {
        libvlc_media_player_stop(player_);
        libvlc_media_player_release(player_);
        player_ = nullptr;
    }
    if (media_ != nullptr)
    {
        libvlc_media_release(media_);
        media_ = nullptr;
    }

    if (plist_ != nullptr)
    {
        libvlc_media_list_player_release(plist_);
        plist_ = nullptr;
    }
    if (list_ != nullptr)
    {
        libvlc_media_list_release(list_);
        list_ = nullptr;
    }
    if (inst_ != nullptr)
    {
        libvlc_release(inst_);
        inst_ = nullptr;
    }

    if (print_buffer != nullptr)
    {
        free(print_buffer);
        print_buffer = nullptr;
    }
    if (out_buffer != nullptr)
    {
        free(out_buffer);
        out_buffer = nullptr;
    }
    if (tmp_buffer != nullptr)
    {
        free(tmp_buffer);
        tmp_buffer = nullptr;
    }
}

Last modified on 2020-04-07