Hi all,
I'm trying to get hardware-accelerated decoding with VideoToolbox working
on iOS. I'm using FFmpeg 3.2, and I'm trying to decode a live TV stream
(streamed from an HD Homerun) with mpegts & h264.
I've got a very simple toy program that works on macOS - see attached
sources. Poc.cpp/h creates and runs the decoder class, and exposes a
function that's callable from C (and Objective-C.) Decoder.cpp/hpp is a
simple C++ class which sets up the appropriate FFmpeg contexts and runs a
simple packet/frame pump. To enable HWA decoding, I set a custom get_format
function on the codec context before opening it; my get_format
implementation then watches to see if AV_PIX_FMT_VIDEOTOOLBOX is an
available option. If so, it calls av_videotoolbox_default_init on the codec
context and returns indicating selection of that format. Again, on macOS
this appears to work as expected, that is, my dummy frame handler function
gets a steady stream of frames, and CPU usage is much lower than it is
without my custom get_format implementation.
However, the same code fails on iOS, and then crashes. Specifically, the
setup code succeeds (or at least, it neither reports nor logs any errors),
then I enter my packet pump, and start pumping a few packets from the demux
to the codec. The first few times, this gets logged:
[h264 @ 0x101982400] Failed to decode frame (-12911)
[h264 @ 0x101982400] hardware accelerator failed to decode picture
...and avcodec_receive_frame returns AVERROR(EAGAIN) (not surprising, as
the codec buffer starts to fill.) Then after a few rounds of that (and
before avcodec_receive_frame actually returns any frames),
avcodec_send_packet crashes, EXC_BAD_ACCESS (code=1, address=0x0) with this
call stack:
#0 0x0000000100274f04 in ff_h264_decode_ref_pic_list_reordering ()
#1 0x000000010027bc58 in ff_h264_decode_slice_header ()
#2 0x000000010023a9c8 in h264_decode_frame ()
#3 0x0000000100596248 in avcodec_decode_video2 ()
#4 0x0000000100597824 in do_decode ()
#5 0x000000010158883c in decoder::run() at
/Users/josh/Git/ffmpeg-sandbox/ffmpeg-poc/ffmpeg-poc/decoder.cpp:85
#6 0x0000000101584e0c in ::run_poc() at
/Users/josh/Git/ffmpeg-sandbox/ffmpeg-poc/ffmpeg-poc/poc.cpp:42
So, is there something that I need to do differently on iOS vs macOS, or is
this a bug, or...?
Or, if nothing else, can anyone point me to an example of working source
code which successfully uses HWA decode on iOS?
*Josh Gustafson*
*Senior Software Engineer*
650.209.0952
www.simplecontrol.com
//
// decoder.cpp
// ffmpeg-poc
//
// Created by Josh Gustafson on 16/11/14.
// Copyright © 2016 Simple Control. All rights reserved.
//
#include "decoder.hpp"
#include <iostream>
#include <string>
#include <sstream>
using std::string;
decoder::decoder(string const& url) {
if (auto result = avformat_open_input(&formatContext, url.c_str(), nullptr, nullptr) < 0) {
std::ostringstream s;
s << "avformat_open_input failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
if (auto result = avformat_find_stream_info(formatContext, nullptr) < 0) {
std::ostringstream s;
s << "avformat_find_stream_info failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
auto videoStreamIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0);
if (videoStreamIndex < 0) {
std::ostringstream s;
s << "av_find_best_stream(video) failed: " << av_err2str(videoStreamIndex);
throw std::runtime_error(s.str());
}
videoStream = formatContext->streams[videoStreamIndex];
assert(!!videoStream);
videoCodecContext = avcodec_alloc_context3(videoCodec);
if (!videoCodecContext) {
std::ostringstream s;
s << "avcodec_alloc_context3(video) failed";
throw std::runtime_error(s.str());
}
if (auto result = avcodec_parameters_to_context(videoCodecContext, videoStream->codecpar) < 0) {
std::ostringstream s;
s << "avcodec_parameters_to_context(video) failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
videoCodecContext->get_format = decoder::get_format;
if (auto result = avcodec_open2(videoCodecContext, videoCodec, nullptr) < 0) {
std::ostringstream s;
s << "avcodec_open2(video) failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
for (auto i = 0; i < formatContext->nb_streams; ++i) {
formatContext->streams[i]->discard = AVDISCARD_ALL;
}
videoStream->discard = AVDISCARD_DEFAULT;
}
decoder::~decoder() {
avcodec_free_context(&videoCodecContext);
avformat_close_input(&formatContext);
}
void decoder::run() {
AVPacket packet = { 0 };
auto frame = av_frame_alloc();
while (true) {
av_packet_unref(&packet);
auto result = av_read_frame(formatContext, &packet);
if (result < 0) {
if (result == AVERROR_EOF) {
avcodec_send_packet(videoCodecContext, NULL);
receive_video_frames(frame);
break;
} else {
std::ostringstream s;
s << "av_read_frame failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
} else {
if (packet.stream_index == videoStream->index) {
avcodec_send_packet(videoCodecContext, &packet);
if (!receive_video_frames(frame)) {
break;
}
}
}
}
av_packet_unref(&packet);
av_frame_free(&frame);
}
AVPixelFormat decoder::get_format(AVCodecContext* context, AVPixelFormat const formats[]) {
for (auto i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
std::cerr << "get_format: format[" << i << "]: " << av_get_pix_fmt_name(formats[i]) << std::endl;
}
for (auto i = 0; formats[i] != AV_PIX_FMT_NONE; ++i)
{
if (formats[i] == AV_PIX_FMT_VIDEOTOOLBOX)
{
auto result = av_videotoolbox_default_init(context);
if (result < 0)
{
std::ostringstream s;
s << "av_videotoolbox_default_init failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
else
{
std::cerr << "get_format: selecting AV_PIX_FMT_VIDEOTOOLBOX" << std::endl;
return AV_PIX_FMT_VIDEOTOOLBOX;
}
}
}
return formats[0];
}
bool decoder::receive_video_frames(AVFrame* frame) {
while (true) {
auto result = avcodec_receive_frame(videoCodecContext, frame);
switch (result) {
case 0: handle_frame(frame); break;
case AVERROR(EAGAIN): return true;
case AVERROR_EOF: return false;
default:
{
std::ostringstream s;
s << "avcodec_receive_frame failed: " << av_err2str(result);
throw std::runtime_error(s.str());
}
}
}
}
void decoder::handle_frame(AVFrame const* frame) {
std::cerr << "Decoded frame: " << frame->width << "x" << frame->height << " " << av_get_pix_fmt_name(static_cast<AVPixelFormat>(frame->format)) << std::endl;
// TODO
}
//
// decoder.hpp
// ffmpeg-poc
//
// Created by Josh Gustafson on 16/11/14.
// Copyright © 2016 Simple Control. All rights reserved.
//
#ifndef decoder_hpp
#define decoder_hpp
#include <string>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/videotoolbox.h>
#include <libavutil/pixdesc.h>
}
class decoder {
public:
decoder(std::string const& url);
~decoder();
void run();
private:
static AVPixelFormat get_format(AVCodecContext* context, AVPixelFormat const formats[]);
AVFormatContext* formatContext = nullptr;
AVStream* videoStream = nullptr;
AVCodec* videoCodec = nullptr;
AVCodecContext* videoCodecContext = nullptr;
bool receive_video_frames(AVFrame* frame);
void handle_frame(AVFrame const* frame);
};
#endif /* decoder_hpp */
//
// poc.cpp
// ffmpeg-poc-ios
//
// Created by Josh Gustafson on 16/11/14.
// Copyright © 2016 Simple Control. All rights reserved.
//
#include "poc.h"
#include <iostream>
#include <string>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/videotoolbox.h>
#include <libavutil/pixdesc.h>
}
#include "decoder.hpp"
using namespace std::string_literals;
using std::string;
namespace {
auto const input_url = "http://192.168.3.32:5004/auto/v660"s;
class avformat_network_context {
public:
avformat_network_context() { avformat_network_init(); }
~avformat_network_context() { avformat_network_deinit(); }
};
}
int run_poc() {
av_register_all();
avformat_network_context c;
try {
decoder d(input_url);
d.run();
return 0;
} catch(std::runtime_error const& e) {
std::cerr << e.what() << std::endl;
return -1;
}
}
//
// poc.hpp
// ffmpeg-poc-ios
//
// Created by Josh Gustafson on 16/11/14.
// Copyright © 2016 Simple Control. All rights reserved.
//
#ifndef poc_hpp
#define poc_hpp
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
EXTERNC int run_poc();
#endif /* poc_hpp */
_______________________________________________
Libav-user mailing list
[email protected]
http://ffmpeg.org/mailman/listinfo/libav-user