/******************************************************************************** Copyright (C) 2012 Hugh Bailey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ********************************************************************************/ #include "Main.h" #include #include extern "C" { #include "../x264/x264.h" } void get_x264_log(void *param, int i_level, const char *psz, va_list argptr) { String chi; chi << TEXT("x264: ") << String(psz); chi.FindReplace(TEXT("%s"), TEXT("%S")); OSDebugOutva(chi, argptr); chi.FindReplace(TEXT("\r"), TEXT("")); chi.FindReplace(TEXT("\n"), TEXT("")); if (chi == TEXT("x264: OpenCL: fatal error, aborting encode") || chi == TEXT("x264: OpenCL: Invalid value.")) { if (App->IsRunning()) { // FIXME: currently due to the way OBS handles the stream report, if reconnect is enabled and this error happens // outside of the 30 second "no reconnect" window, no error dialog is shown to the user. usually x264 opencl errors // will happen immediately though. Log(TEXT("Aborting stream due to x264 opencl error")); App->SetStreamReport(TEXT("x264 OpenCL Error\r\n\r\nx264 encountered an error attempting to use OpenCL. This may be due to unsupported hardware or outdated drivers.\r\n\r\nIt is recommended that you remove opencl=true from your x264 advanced settings.")); PostMessage(hwndMain, OBS_REQUESTSTOP, 1, 0); } } Logva(chi.Array(), argptr); } struct VideoPacket { List Packet; inline void FreeData() {Packet.Clear();} }; const float baseCRF = 22.0f; bool valid_x264_string(const String &str, const char **x264StringList) { do { if(str.CompareI(String(*x264StringList))) return true; } while (*++x264StringList != 0); return false; } class X264Encoder : public VideoEncoder { x264_param_t paramData; x264_t *x264; x264_picture_t picOut; int cur_pts_time; x264_nal_t *pp_nal; int pi_nal; int fps_ms; bool bRequestKeyframe; UINT width, height; String curPreset, curTune, curProfile; bool bFirstFrameProcessed; bool bUseCBR, bUseCFR, bPadCBR; List CurrentPackets; List HeaderPacket, SEIData; INT64 delayOffset; int frameShift; inline void ClearPackets() { for(UINT i=0; iGetString(TEXT("Video Encoding"), TEXT("X264Profile"), TEXT("high")); BOOL bUseCustomParams = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCustomSettings")); if(bUseCustomParams) { String strCustomParams = AppConfig->GetString(TEXT("Video Encoding"), TEXT("CustomSettings")); strCustomParams.KillSpaces(); if(strCustomParams.IsValid()) { Log(TEXT("Using custom x264 settings: \"%s\""), strCustomParams.Array()); strCustomParams.GetTokenList(paramList, ' ', FALSE); for(UINT i=0; i &packets, List &packetTypes, DWORD outputTimestamp, DWORD &out_pts) { x264_picture_t *picIn = (x264_picture_t*)picInPtr; x264_nal_t *nalOut; int nalNum; packets.Clear(); ClearPackets(); if(bRequestKeyframe && picIn) picIn->i_type = X264_TYPE_IDR; if(x264_encoder_encode(x264, &nalOut, &nalNum, picIn, &picOut) < 0) { AppWarning(TEXT("x264 encode failed")); return false; } if(bRequestKeyframe && picIn) { picIn->i_type = X264_TYPE_AUTO; bRequestKeyframe = false; } if(!bFirstFrameProcessed && nalNum) { delayOffset = -picOut.i_dts; bFirstFrameProcessed = true; } INT64 ts = INT64(outputTimestamp); int timeOffset; //if frame duplication is being used, the shift will be insignificant, so just don't bother adjusting audio timeOffset = int(picOut.i_pts-picOut.i_dts); timeOffset += frameShift; out_pts = (DWORD)picOut.i_pts; if(nalNum && timeOffset < 0) { frameShift -= timeOffset; timeOffset = 0; } //OSDebugOut(TEXT("inpts: %005lld, dts: %005lld, pts: %005lld, timestamp: %005d, offset: %005d, newoffset: %005lld\n"), picIn->i_pts, picOut.i_dts, picOut.i_pts, outputTimestamp, timeOffset, picOut.i_pts-picOut.i_dts); timeOffset = htonl(timeOffset); BYTE *timeOffsetAddr = ((BYTE*)&timeOffset)+1; VideoPacket *newPacket = NULL; PacketType bestType = PacketType_VideoDisposable; bool bFoundFrame = false; for(int i=0; iPacket); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } } else if(nal.i_type == NAL_FILLER) { BYTE *skip = nal.p_payload; while(*(skip++) != 0x1); int skipBytes = (int)(skip-nal.p_payload); int newPayloadSize = (nal.i_payload-skipBytes); if (!newPacket) newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer packetOut(newPacket->Packet); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } else if(nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SLICE) { BYTE *skip = nal.p_payload; while(*(skip++) != 0x1); int skipBytes = (int)(skip-nal.p_payload); if (!newPacket) newPacket = CurrentPackets.CreateNew(); if (!bFoundFrame) { newPacket->Packet.Insert(0, (nal.i_type == NAL_SLICE_IDR) ? 0x17 : 0x27); newPacket->Packet.Insert(1, 1); newPacket->Packet.InsertArray(2, timeOffsetAddr, 3); bFoundFrame = true; } int newPayloadSize = (nal.i_payload-skipBytes); BufferOutputSerializer packetOut(newPacket->Packet); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); switch(nal.i_ref_idc) { case NAL_PRIORITY_DISPOSABLE: bestType = MAX(bestType, PacketType_VideoDisposable); break; case NAL_PRIORITY_LOW: bestType = MAX(bestType, PacketType_VideoLow); break; case NAL_PRIORITY_HIGH: bestType = MAX(bestType, PacketType_VideoHigh); break; case NAL_PRIORITY_HIGHEST: bestType = MAX(bestType, PacketType_VideoHighest); break; } } /*else if(nal.i_type == NAL_SPS) { VideoPacket *newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer headerOut(newPacket->Packet); headerOut.OutputByte(0x17); headerOut.OutputByte(0); headerOut.Serialize(timeOffsetAddr, 3); headerOut.OutputByte(1); headerOut.Serialize(nal.p_payload+5, 3); headerOut.OutputByte(0xff); headerOut.OutputByte(0xe1); headerOut.OutputWord(htons(nal.i_payload-4)); headerOut.Serialize(nal.p_payload+4, nal.i_payload-4); x264_nal_t &pps = nalOut[i+1]; //the PPS always comes after the SPS headerOut.OutputByte(1); headerOut.OutputWord(htons(pps.i_payload-4)); headerOut.Serialize(pps.p_payload+4, pps.i_payload-4); }*/ else continue; } packetTypes << bestType; packets.SetSize(CurrentPackets.Num()); for(UINT i=0; i%d", old_bitrate, maxBitrate); if (old_buffer != bufferSize) changes << FormattedString(L"%sbuffer size %d->%d", changes.Length() ? L", " : L"", old_buffer, bufferSize); if (changes) Log(L"x264: %s", changes.Array()); } return retVal == 0; } virtual void RequestKeyframe() { bRequestKeyframe = true; } virtual int GetBufferedFrames() { return x264_encoder_delayed_frames(x264); } virtual bool HasBufferedFrames() { return x264_encoder_delayed_frames(x264) > 0; } }; VideoEncoder* CreateX264Encoder(int fps, int width, int height, int quality, CTSTR preset, bool bUse444, ColorDescription &colorDesc, int maxBitRate, int bufferSize, bool bUseCFR) { return new X264Encoder(fps, width, height, quality, preset, bUse444, colorDesc, maxBitRate, bufferSize, bUseCFR); }